/*-------------------------------------------------------------------------
 *
 * analyze.c
 *	  将原始解析树转换为查询树
 *
 * 对于可优化的语句，我们要小心地在每个引用的表上
 * 获取适当的锁，后端的其他模块在依赖结果之前保持或
 * 重新获取这些锁。因此，对这些语句进行显著的语义分析
 * 是可以的。对于工具命令，此处不获取锁（如果获取了，
 * 我们不能确保在执行时仍然拥有它们）。因此，工具命令的一般规则
 * 是将其直接转储到 Query 节点中而不转换。
 * DECLARE CURSOR，EXPLAIN 和 CREATE TABLE AS 是例外，
 * 因为它们包含可优化的语句，我们应该对其进行转换。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *	src/backend/parser/analyze.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/sysattr.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_cte.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_merge.h"
#include "parser/parse_oper.h"
#include "parser/parse_param.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/backend_status.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/queryjumble.h"
#include "utils/rel.h"
#include "utils/syscache.h"


/* 插件在解析分析结束时获得控制的钩子 */
post_parse_analyze_hook_type post_parse_analyze_hook = NULL;

static Query *fc_transformOptionalSelectInto(ParseState *fc_pstate, Node *fc_parseTree);
static Query *fc_transformDeleteStmt(ParseState *fc_pstate, DeleteStmt *fc_stmt);
static Query *fc_transformInsertStmt(ParseState *fc_pstate, InsertStmt *fc_stmt);
static OnConflictExpr *fc_transformOnConflictClause(ParseState *fc_pstate,
												 OnConflictClause *fc_onConflictClause);
static int	fc_count_rowexpr_columns(ParseState *fc_pstate, Node *fc_expr);
static Query *fc_transformSelectStmt(ParseState *fc_pstate, SelectStmt *fc_stmt);
static Query *fc_transformValuesClause(ParseState *fc_pstate, SelectStmt *fc_stmt);
static Query *fc_transformSetOperationStmt(ParseState *fc_pstate, SelectStmt *fc_stmt);
static Node *fc_transformSetOperationTree(ParseState *fc_pstate, SelectStmt *fc_stmt,
									   bool fc_isTopLevel, List **fc_targetlist);
static void fc_determineRecursiveColTypes(ParseState *fc_pstate,
									   Node *fc_larg, List *fc_nrtargetlist);
static Query *fc_transformReturnStmt(ParseState *fc_pstate, ReturnStmt *fc_stmt);
static Query *fc_transformUpdateStmt(ParseState *fc_pstate, UpdateStmt *fc_stmt);
static List *fc_transformReturningList(ParseState *fc_pstate, List *fc_returningList);
static Query *fc_transformPLAssignStmt(ParseState *fc_pstate,
									PLAssignStmt *fc_stmt);
static Query *fc_transformDeclareCursorStmt(ParseState *fc_pstate,
										 DeclareCursorStmt *fc_stmt);
static Query *fc_transformExplainStmt(ParseState *fc_pstate,
								   ExplainStmt *fc_stmt);
static Query *fc_transformCreateTableAsStmt(ParseState *fc_pstate,
										 CreateTableAsStmt *fc_stmt);
static Query *fc_transformCallStmt(ParseState *fc_pstate,
								CallStmt *fc_stmt);
static void fc_transformLockingClause(ParseState *fc_pstate, Query *fc_qry,
								   LockingClause *fc_lc, bool fc_pushedDown);
#ifdef RAW_EXPRESSION_COVERAGE_TEST
static bool fc_test_raw_expression_coverage(Node *fc_node, void *fc_context);
#endif


/*
 * parse_analyze_fixedparams
 *		分析一个原始解析树并将其转换为查询形式。
 *
 * 可选地，可以提供有关 $n 参数类型的信息。
 * 对 paramTypes[] 中未定义的 $n 索引的引用是不允许的。
 *
 * 结果是一个查询节点。可优化的语句需要大量
 * 转换，而实用类型的语句仅简要挂靠在
 * 一个虚拟 CMD_UTILITY 查询节点上。
 */
Query * parse_analyze_fixedparams(RawStmt *fc_parseTree, const char *fc_sourceText,
						  const Oid *fc_paramTypes, int fc_numParams,
						  QueryEnvironment *fc_queryEnv)
{
	ParseState *fc_pstate = make_parsestate(NULL);
	Query	   *fc_query;
	JumbleState *fc_jstate = NULL;

	Assert(fc_sourceText != NULL); /* 从 8.4 开始要求 */

	fc_pstate->p_sourcetext = fc_sourceText;

	if (fc_numParams > 0)
		setup_parse_fixed_parameters(fc_pstate, fc_paramTypes, fc_numParams);

	fc_pstate->p_queryEnv = fc_queryEnv;

	fc_query = transformTopLevelStmt(fc_pstate, fc_parseTree);

	if (IsQueryIdEnabled())
		fc_jstate = JumbleQuery(fc_query, fc_sourceText);

	if (post_parse_analyze_hook)
		(*post_parse_analyze_hook) (fc_pstate, fc_query, fc_jstate);

	free_parsestate(fc_pstate);

	pgstat_report_query_id(fc_query->queryId, false);

	return fc_query;
}

/*
 * parse_analyze_varparams
 *
 * 当可以根据上下文推断有关 $n 符号数据类型的信息时使用此变体。
 * 传入的 paramTypes[] 数组可以通过 repalloc 修改或扩大。
 */
Query * parse_analyze_varparams(RawStmt *fc_parseTree, const char *fc_sourceText,
						Oid **fc_paramTypes, int *fc_numParams,
						QueryEnvironment *fc_queryEnv)
{
	ParseState *fc_pstate = make_parsestate(NULL);
	Query	   *fc_query;
	JumbleState *fc_jstate = NULL;

	Assert(fc_sourceText != NULL); /* 从 8.4 开始要求 */

	fc_pstate->p_sourcetext = fc_sourceText;

	setup_parse_variable_parameters(fc_pstate, fc_paramTypes, fc_numParams);

	fc_pstate->p_queryEnv = fc_queryEnv;

	fc_query = transformTopLevelStmt(fc_pstate, fc_parseTree);

	/* 确保参数类型一切正常 */
	check_variable_parameters(fc_pstate, fc_query);

	if (IsQueryIdEnabled())
		fc_jstate = JumbleQuery(fc_query, fc_sourceText);

	if (post_parse_analyze_hook)
		(*post_parse_analyze_hook) (fc_pstate, fc_query, fc_jstate);

	free_parsestate(fc_pstate);

	pgstat_report_query_id(fc_query->queryId, false);

	return fc_query;
}

/*
 * parse_analyze_withcb
 *
 * 当调用者提供自己的解析器回调以解析参数和可能的其他事物时使用此变体。
 */
Query * parse_analyze_withcb(RawStmt *fc_parseTree, const char *fc_sourceText,
					 ParserSetupHook fc_parserSetup,
					 void *fc_parserSetupArg,
					 QueryEnvironment *fc_queryEnv)
{
	ParseState *fc_pstate = make_parsestate(NULL);
	Query	   *fc_query;
	JumbleState *fc_jstate = NULL;

	Assert(fc_sourceText != NULL); /* 从 8.4 开始要求 */

	fc_pstate->p_sourcetext = fc_sourceText;
	fc_pstate->p_queryEnv = fc_queryEnv;
	(*fc_parserSetup) (fc_pstate, fc_parserSetupArg);

	fc_query = transformTopLevelStmt(fc_pstate, fc_parseTree);

	if (IsQueryIdEnabled())
		fc_jstate = JumbleQuery(fc_query, fc_sourceText);

	if (post_parse_analyze_hook)
		(*post_parse_analyze_hook) (fc_pstate, fc_query, fc_jstate);

	free_parsestate(fc_pstate);

	pgstat_report_query_id(fc_query->queryId, false);

	return fc_query;
}


/*
 * parse_sub_analyze
 *		递归分析子语句的入口点。
 */
Query * parse_sub_analyze(Node *fc_parseTree, ParseState *fc_parentParseState,
				  CommonTableExpr *fc_parentCTE,
				  bool fc_locked_from_parent,
				  bool fc_resolve_unknowns)
{
	ParseState *fc_pstate = make_parsestate(fc_parentParseState);
	Query	   *fc_query;

	fc_pstate->p_parent_cte = fc_parentCTE;
	fc_pstate->p_locked_from_parent = fc_locked_from_parent;
	fc_pstate->p_resolve_unknowns = fc_resolve_unknowns;

	fc_query = transformStmt(fc_pstate, fc_parseTree);

	free_parsestate(fc_pstate);

	return fc_query;
}

/*
 * transformTopLevelStmt -
 *	  将解析树转换为查询树。
 *
 * 此函数仅负责将语句位置数据从 RawStmt 转移到完成的查询中。
 */
Query * transformTopLevelStmt(ParseState *fc_pstate, RawStmt *fc_parseTree)
{
	Query	   *fc_result;

	/* 我们处于顶层，所以允许 SELECT INTO */
	fc_result = fc_transformOptionalSelectInto(fc_pstate, fc_parseTree->stmt);

	fc_result->stmt_location = fc_parseTree->stmt_location;
	fc_result->stmt_len = fc_parseTree->stmt_len;

	return fc_result;
}

/*
 * transformOptionalSelectInto -
 *	  如果 SELECT 有 INTO，则将其转换为 CREATE TABLE AS。
 *
 * 我们在这里唯一做的事情是将 SELECT ... INTO 转换为 CREATE TABLE AS。
 * 由于在更大语句中不允许使用实用语句，因此这仅在解析树的顶部允许，
 * 所以我们只在进入递归的 transformStmt() 处理之前尝试这样做。
 */
static Query * fc_transformOptionalSelectInto(ParseState *fc_pstate, Node *fc_parseTree)
{
	if (IsA(fc_parseTree, SelectStmt))
	{
		SelectStmt *fc_stmt = (SelectStmt *) fc_parseTree;

		/* 如果这是一个集合操作树，深入左侧的 SelectStmt */
		while (fc_stmt && fc_stmt->op != SETOP_NONE)
			fc_stmt = fc_stmt->larg;
		Assert(fc_stmt && IsA(fc_stmt, SelectStmt) && fc_stmt->larg == NULL);

		if (fc_stmt->intoClause)
		{
			CreateTableAsStmt *fc_ctas = makeNode(CreateTableAsStmt);

			fc_ctas->query = fc_parseTree;
			fc_ctas->into = fc_stmt->intoClause;
			fc_ctas->objtype = OBJECT_TABLE;
			fc_ctas->is_select_into = true;

			/*
			 * 从 SelectStmt 中移除 intoClause。这使得 transformSelectStmt 如果发现
			 * 设置了 intoClause 时，可以安全地抱怨（暗示 INTO 出现在不允许的位置）。
			 */
			fc_stmt->intoClause = NULL;

			fc_parseTree = (Node *) fc_ctas;
		}
	}

	return transformStmt(fc_pstate, fc_parseTree);
}

/*
 * transformStmt -
 *	  递归地将解析树转换为查询树。
 */
Query * transformStmt(ParseState *fc_pstate, Node *fc_parseTree)
{
	Query	   *fc_result;

	/*
	 * 我们对基本 DML 语句应用 RAW_EXPRESSION_COVERAGE_TEST 测试；
	 * 我们不能仅仅在所有内容上运行它，因为 raw_expression_tree_walker()
	 * 并不声称处理实用语句。
	 */
#ifdef RAW_EXPRESSION_COVERAGE_TEST
	switch (nodeTag(fc_parseTree))
	{
		case T_SelectStmt:
		case T_InsertStmt:
		case T_UpdateStmt:
		case T_DeleteStmt:
		case T_MergeStmt:
			(void) fc_test_raw_expression_coverage(fc_parseTree, NULL);
			break;
		default:
			break;
	}
#endif							/* RAW_EXPRESSION_COVERAGE_TEST */

	/*
	 * 注意：在更改此处具有非默认处理的语句类型集时，请参阅 stmt_requires_parse_analysis() 和 analyze_requires_snapshot()。
	 */
	switch (nodeTag(fc_parseTree))
	{
			/*
			 * 可优化的语句
			 */
		case T_InsertStmt:
			fc_result = fc_transformInsertStmt(fc_pstate, (InsertStmt *) fc_parseTree);
			break;

		case T_DeleteStmt:
			fc_result = fc_transformDeleteStmt(fc_pstate, (DeleteStmt *) fc_parseTree);
			break;

		case T_UpdateStmt:
			fc_result = fc_transformUpdateStmt(fc_pstate, (UpdateStmt *) fc_parseTree);
			break;

		case T_MergeStmt:
			fc_result = transformMergeStmt(fc_pstate, (MergeStmt *) fc_parseTree);
			break;

		case T_SelectStmt:
			{
				SelectStmt *fc_n = (SelectStmt *) fc_parseTree;

				if (fc_n->valuesLists)
					fc_result = fc_transformValuesClause(fc_pstate, fc_n);
				else if (fc_n->op == SETOP_NONE)
					fc_result = fc_transformSelectStmt(fc_pstate, fc_n);
				else
					fc_result = fc_transformSetOperationStmt(fc_pstate, fc_n);
			}
			break;

		case T_ReturnStmt:
			fc_result = fc_transformReturnStmt(fc_pstate, (ReturnStmt *) fc_parseTree);
			break;

		case T_PLAssignStmt:
			fc_result = fc_transformPLAssignStmt(fc_pstate,
										   (PLAssignStmt *) fc_parseTree);
			break;

			/*
			 * 特殊情况
			 */
		case T_DeclareCursorStmt:
			fc_result = fc_transformDeclareCursorStmt(fc_pstate,
												(DeclareCursorStmt *) fc_parseTree);
			break;

		case T_ExplainStmt:
			fc_result = fc_transformExplainStmt(fc_pstate,
										  (ExplainStmt *) fc_parseTree);
			break;

		case T_CreateTableAsStmt:
			fc_result = fc_transformCreateTableAsStmt(fc_pstate,
												(CreateTableAsStmt *) fc_parseTree);
			break;

		case T_CallStmt:
			fc_result = fc_transformCallStmt(fc_pstate,
									   (CallStmt *) fc_parseTree);
			break;

		default:

			/*
			 * 其他语句不需要任何转换；只需返回原始的解析树，并在其上加上一个 Query 节点。
			 */
			fc_result = makeNode(Query);
			fc_result->commandType = CMD_UTILITY;
			fc_result->utilityStmt = (Node *) fc_parseTree;
			break;
	}

	/* 标记为原始查询，直到我们了解不同的情况 */
	fc_result->querySource = QSRC_ORIGINAL;
	fc_result->canSetTag = true;

	return fc_result;
}

/*
 * stmt_requires_parse_analysis
 *		如果解析分析对给定的原始解析树将执行任何非平凡的操作，则返回 true。
 *
 * 通常，对于 transformStmt() 做的事情超过包裹一个 CMD_UTILITY Query 的任何语句类型，都应该返回 true。
 * 当返回 false 时，调用者可以假定在原始语句的解析分析中没有需要重新进行的情况。
 *
 * 目前，由于重写器和规划程序对 CMD_UTILITY Queries 不执行任何操作，因此 false 结果意味着整个解析分析/重写/计划
 * 流程将永远不需要重新进行。如果以后有所变化，调用者可能需要调整。
 */
bool stmt_requires_parse_analysis(RawStmt *fc_parseTree)
{
	bool		fc_result;

	switch (nodeTag(fc_parseTree->stmt))
	{
			/*
			 * 可优化的语句
			 */
		case T_InsertStmt:
		case T_DeleteStmt:
		case T_UpdateStmt:
		case T_MergeStmt:
		case T_SelectStmt:
		case T_ReturnStmt:
		case T_PLAssignStmt:
			fc_result = true;
			break;

			/*
			 * 特殊情况
			 */
		case T_DeclareCursorStmt:
		case T_ExplainStmt:
		case T_CreateTableAsStmt:
		case T_CallStmt:
			fc_result = true;
			break;

		default:
			/* 所有其他语句仅需包裹在 CMD_UTILITY Query 中 */
			fc_result = false;
			break;
	}

	return fc_result;
}

/*
 * analyze_requires_snapshot
 *		如果在对给定的原始解析树进行解析分析之前必须设置快照，则返回 true。
 */
bool analyze_requires_snapshot(RawStmt *fc_parseTree)
{
	/*
	 * 目前，这应该在与 stmt_requires_parse_analysis() 完全相同的情况下返回 true，因此我们只会调用该函数
	 * 而不是重复它。我们将两个入口点分开以便于调用者理解，因为从调用者的角度来看，这些是不同的条件。
	 *
	 * 虽然将来可能会有一种语句类型，对于这种类型，transformStmt() 做了一些非平凡的工作，但不需要快照进行该处理，
	 * 但看起来做出这样的选择将是脆弱的。如果你想安装一个例外，请在注释中记录理由。
	 */
	return stmt_requires_parse_analysis(fc_parseTree);
}

/*
 * transformDeleteStmt -
 *	  转换一个 Delete 语句
 */
static Query * fc_transformDeleteStmt(ParseState *fc_pstate, DeleteStmt *fc_stmt)
{
	Query	   *fc_qry = makeNode(Query);
	ParseNamespaceItem *fc_nsitem;
	Node	   *fc_qual;

	fc_qry->commandType = CMD_DELETE;

	/* 独立于其他内容处理 WITH 子句 */
	if (fc_stmt->withClause)
	{
		fc_qry->hasRecursive = fc_stmt->withClause->recursive;
		fc_qry->cteList = transformWithClause(fc_pstate, fc_stmt->withClause);
		fc_qry->hasModifyingCTE = fc_pstate->p_hasModifyingCTE;
	}

	/* 仅设置结果关系的范围表 */
	fc_qry->resultRelation = setTargetTable(fc_pstate, fc_stmt->relation,
										 fc_stmt->relation->inh,
										 true,
										 ACL_DELETE);
	fc_nsitem = fc_pstate->p_target_nsitem;

	/* DELETE 中没有 DISTINCT */
	fc_qry->distinctClause = NIL;

	/* USING 中的子查询无法访问结果关系 */
	fc_nsitem->p_lateral_only = true;
	fc_nsitem->p_lateral_ok = false;

	/*
	 * USING 子句是非标准的 SQL 语法，其功能等同于可以为 UPDATE 指定的 FROM 列表。
	 * 使用 USING 关键字而不是 FROM，因为 FROM 已经是 DELETE 语法中的一个关键字。
	 */
	transformFromClause(fc_pstate, fc_stmt->usingClause);

	/* 剩余的子句可以正常引用结果关系 */
	fc_nsitem->p_lateral_only = false;
	fc_nsitem->p_lateral_ok = true;

	fc_qual = transformWhereClause(fc_pstate, fc_stmt->whereClause,
								EXPR_KIND_WHERE, "WHERE");

	fc_qry->returningList = fc_transformReturningList(fc_pstate, fc_stmt->returningList);

	/* 已完成范围表和连接树的构建 */
	fc_qry->rtable = fc_pstate->p_rtable;
	fc_qry->jointree = makeFromExpr(fc_pstate->p_joinlist, fc_qual);

	fc_qry->hasSubLinks = fc_pstate->p_hasSubLinks;
	fc_qry->hasWindowFuncs = fc_pstate->p_hasWindowFuncs;
	fc_qry->hasTargetSRFs = fc_pstate->p_hasTargetSRFs;
	fc_qry->hasAggs = fc_pstate->p_hasAggs;

	assign_query_collations(fc_pstate, fc_qry);

	/* 必须在排序后完成，以确保可靠的表达式比较 */
	if (fc_pstate->p_hasAggs)
		parseCheckAggregates(fc_pstate, fc_qry);

	return fc_qry;
}

/*
 * transformInsertStmt -
 *	  转换一个 Insert 语句
 */
static Query * fc_transformInsertStmt(ParseState *fc_pstate, InsertStmt *fc_stmt)
{
	Query	   *fc_qry = makeNode(Query);
	SelectStmt *fc_selectStmt = (SelectStmt *) fc_stmt->selectStmt;
	List	   *fc_exprList = NIL;
	bool		fc_isGeneralSelect;
	List	   *fc_sub_rtable;
	List	   *fc_sub_namespace;
	List	   *fc_icolumns;
	List	   *fc_attrnos;
	ParseNamespaceItem *fc_nsitem;
	RangeTblEntry *fc_rte;
	ListCell   *fc_icols;
	ListCell   *fc_attnos;
	ListCell   *fc_lc;
	bool		fc_isOnConflictUpdate;
	AclMode		fc_targetPerms;

	/* 不能有外部的 WITH 需要考虑 */
	Assert(fc_pstate->p_ctenamespace == NIL);

	fc_qry->commandType = CMD_INSERT;
	fc_pstate->p_is_insert = true;

	/* 独立于其他内容处理 WITH 子句 */
	if (fc_stmt->withClause)
	{
		fc_qry->hasRecursive = fc_stmt->withClause->recursive;
		fc_qry->cteList = transformWithClause(fc_pstate, fc_stmt->withClause);
		fc_qry->hasModifyingCTE = fc_pstate->p_hasModifyingCTE;
	}

	fc_qry->override = fc_stmt->override;

	fc_isOnConflictUpdate = (fc_stmt->onConflictClause &&
						  fc_stmt->onConflictClause->action == ONCONFLICT_UPDATE);

	/*
	 * 我们有三种情况需要处理：DEFAULT VALUES (selectStmt == NULL)，
	 * VALUES 列表或一般 SELECT 输入。我们特殊处理 VALUES，既出于效率考虑，也因为
	 * 我们可以处理 DEFAULT 规范。
	 *
	 * 语法允许将 ORDER BY、LIMIT、FOR UPDATE 或 WITH 附加到 VALUES 子句。
	 * 如果我们有这些情况，请将其视为一般的 SELECT；这样它将起作用，但不能将 DEFAULT 项与这些一起使用。
	 */
	fc_isGeneralSelect = (fc_selectStmt && (fc_selectStmt->valuesLists == NIL ||
									  fc_selectStmt->sortClause != NIL ||
									  fc_selectStmt->limitOffset != NULL ||
									  fc_selectStmt->limitCount != NULL ||
									  fc_selectStmt->lockingClause != NIL ||
									  fc_selectStmt->withClause != NULL));

	/*
	 * 如果传入了非空的 rangetable/namespace，并且我们正在执行 INSERT/SELECT，
	 * 则安排将 rangetable/namespace 传递到底部的 SELECT。只有当我们在 CREATE RULE 中
	 * 时，这才会发生，在这种情况下，我们希望规则的 OLD 和 NEW rtable 条目作为 SELECT 的 rtable
	 * 的一部分出现，而不是作为它的外部引用。(小技巧！)但是，SELECT 的连接列表不受影响。
	 * 我们必须在将目标表添加到 INSERT 的 rtable 之前完成此操作。
	 */
	if (fc_isGeneralSelect)
	{
		fc_sub_rtable = fc_pstate->p_rtable;
		fc_pstate->p_rtable = NIL;
		fc_sub_namespace = fc_pstate->p_namespace;
		fc_pstate->p_namespace = NIL;
	}
	else
	{
		fc_sub_rtable = NIL;		/* 未使用，但保持编译器安静 */
		fc_sub_namespace = NIL;
	}

	/*
	 * 在扫描 SELECT 之前必须对 INSERT 目标表获取写锁，否则如果目标表也在 SELECT 部分中提到，
	 * 我们将抓取错误类型的初始锁。请注意，目标表不会添加到连接列表或命名空间中。
	 */
	fc_targetPerms = ACL_INSERT;
	if (fc_isOnConflictUpdate)
		fc_targetPerms |= ACL_UPDATE;
	fc_qry->resultRelation = setTargetTable(fc_pstate, fc_stmt->relation,
										 false, false, fc_targetPerms);

	/* 验证 stmt->cols 列表，或者在未给出列表的情况下构建默认列表 */
	fc_icolumns = checkInsertTargets(fc_pstate, fc_stmt->cols, &fc_attrnos);
	Assert(list_length(fc_icolumns) == list_length(fc_attrnos));

	/*
	 * 确定我们拥有哪种变体的 INSERT。
	 */
	if (fc_selectStmt == NULL)
	{
		/*
		 * 我们有 INSERT ... DEFAULT VALUES。我们可以通过
		 * 输出一个空的目标列表来处理这种情况——在规划器扩展目标列表时，所有列都将使用默认值。
		 */
		fc_exprList = NIL;
	}
	else if (fc_isGeneralSelect)
	{
		/*
		 * 我们使子 pstate 成为外部 pstate 的子节点，以便它可以看到上面提供的任何 Param 定义。
		 * 由于外部 pstate 的 rtable 和命名空间目前是空的，因此暴露子 SELECT 不应该能够看到的名字
		 * 没有副作用。
		 */
		ParseState *fc_sub_pstate = make_parsestate(fc_pstate);
		Query	   *fc_selectQuery;

		
/*
		 * 处理源 SELECT。
		 *
		 * 重要的是，这必须像独立的 SELECT 一样处理；
		 * 否则，INSERT 中的 SELECT 的行为可能会与独立的 SELECT 不同。
		 * （事实上，Postgres 在 6.5 之前就有这样的错误……）
		 *
		 * 唯一的例外是，我们防止将未知类型
		 * 的输出解析为 TEXT。这并不会改变语义，因为如果列类型在语义上很重要，它最终会被解析为
		 * 其他类型。这样做让我们将这样的输出解析为
		 * 目标列的类型，这一点我们将在下面处理。
		 */
		fc_sub_pstate->p_rtable = fc_sub_rtable;
		fc_sub_pstate->p_joinexprs = NIL;	/* sub_rtable 没有连接 */
		fc_sub_pstate->p_namespace = fc_sub_namespace;
		fc_sub_pstate->p_resolve_unknowns = false;

		fc_selectQuery = transformStmt(fc_sub_pstate, fc_stmt->selectStmt);

		free_parsestate(fc_sub_pstate);

		/* 语法应该已经生成一个 SELECT */
		if (!IsA(fc_selectQuery, Query) ||
			fc_selectQuery->commandType != CMD_SELECT)
			elog(ERROR, "unexpected non-SELECT command in INSERT ... SELECT");

		/*
		 * 使源成为INSERT的范围表中的子查询，并将其添加到INSERT的连接列表中（但不在命名空间中）。
		 */
		fc_nsitem = addRangeTableEntryForSubquery(fc_pstate,
											   fc_selectQuery,
											   makeAlias("*SELECT*", NIL),
											   false,
											   false);
		addNSItemToQuery(fc_pstate, fc_nsitem, true, false, false);

		/*----------
		 * 为INSERT生成一个表达式列表，该列表从子查询中选择所有非resjunk列。
		 * （INSERT的tlist必须与子查询的tlist分开，因为我们可能会添加列，
		 * 插入数据类型强制转换等。）
		 *
		 * HACK: 在SELECT的目标列表中，未知类型常量和参数
		 * 被照原样复制而不是作为子查询输出引用。这是为了确保
		 * 当我们尝试将它们强制转换为目标列的数据类型时，正确的事情发生（参见
		 * coerce_type中的特殊情况）。否则，这将失败：
		 *		INSERT INTO foo SELECT 'bar', ... FROM baz
		 *----------
		 */
		fc_exprList = NIL;
		foreach(fc_lc, fc_selectQuery->targetList)
		{
			TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_lc);
			Expr	   *fc_expr;

			if (fc_tle->resjunk)
				continue;
			if (fc_tle->expr &&
				(IsA(fc_tle->expr, Const) || IsA(fc_tle->expr, Param)) &&
				exprType((Node *) fc_tle->expr) == UNKNOWNOID)
				fc_expr = fc_tle->expr;
			else
			{
				Var		   *fc_var = makeVarFromTargetEntry(fc_nsitem->p_rtindex, fc_tle);

				fc_var->location = exprLocation((Node *) fc_tle->expr);
				fc_expr = (Expr *) fc_var;
			}
			fc_exprList = lappend(fc_exprList, fc_expr);
		}

		/* 准备行以分配给目标表 */
		fc_exprList = transformInsertRow(fc_pstate, fc_exprList,
									  fc_stmt->cols,
									  fc_icolumns, fc_attrnos,
									  false);
	}
	else if (list_length(fc_selectStmt->valuesLists) > 1)
	{
		/*
		 * 处理多个VALUES子列表的INSERT ... VALUES。我们
		 * 生成一个VALUES RTE，持有变换后的表达式列表，并
		 * 构建一个包含引用VALUES RTE的Vars的目标列表。
		 */
		List	   *fc_exprsLists = NIL;
		List	   *fc_coltypes = NIL;
		List	   *fc_coltypmods = NIL;
		List	   *fc_colcollations = NIL;
		int			fc_sublist_length = -1;
		bool		fc_lateral = false;

		Assert(fc_selectStmt->intoClause == NULL);

		foreach(fc_lc, fc_selectStmt->valuesLists)
		{
			List	   *fc_sublist = (List *) lfirst(fc_lc);

			/*
			 * 进行基本的表达式转换（与ROW()表达式相同，但允许在顶层进行SetToDefault）
			 */
			fc_sublist = transformExpressionList(fc_pstate, fc_sublist,
											  EXPR_KIND_VALUES, true);

			/*
			 * 所有子列表在转换后必须具有相同的长度（可能将'*'扩展为多个项目）。
			 * VALUES RTE无法处理任何不同的内容。
			 */
			if (fc_sublist_length < 0)
			{
				/* 记住转换后第一个子列表的长度 */
				fc_sublist_length = list_length(fc_sublist);
			}
			else if (fc_sublist_length != list_length(fc_sublist))
			{
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("VALUES lists must all be the same length"),
						 parser_errposition(fc_pstate,
											exprLocation((Node *) fc_sublist))));
			}

			/*
			 * 准备行以分配给目标表。我们通常处理目标列规格上的任何
			 * 间接引用，但随后会剥离结果字段/数组赋值节点，因为我们不
			 * 希望解析的语句在每个VALUES行中包含这些的副本。
			 * （反复转换间接引用规格很烦人，但避免这样做
			 * 需要对transformAssignmentIndirection进行一些非常凌乱的重构。）
			 */
			fc_sublist = transformInsertRow(fc_pstate, fc_sublist,
										 fc_stmt->cols,
										 fc_icolumns, fc_attrnos,
										 true);

			/*
			 * 我们现在必须分配排序规则，因为assign_query_collations
			 * 不处理范围表条目。我们只需在每一行中独立分配所有的
			 * 排序规则，而不担心它们在垂直方向上的一致性。
			 * 外部INSERT查询不会关心VALUES列的排序规则，
			 * 因此在这里识别每一个的共同排序规则并不值得。
			 * （但请注意，这确实有一个可见的影响：INSERT ... VALUES
			 * 不会抱怨列中冲突的显式COLLATE，而在其他上下文中
			 * 相同的VALUES构造会抱怨。）
			 */
			assign_list_collations(fc_pstate, fc_sublist);

			fc_exprsLists = lappend(fc_exprsLists, fc_sublist);
		}

		/*
		 * 为VALUES RTE构造列类型/typmod/排序规则列表。
		 * 每个列中的每个表达式都已强制转换为相应目标列或子字段的类型/typmod，
		 * 因此，查看第一行的exprType/exprTypmod就足够了。
		 * 我们不关心排序规则标记，因此只是用InvalidOid填充。
		 */
		foreach(fc_lc, (List *) linitial(fc_exprsLists))
		{
			Node	   *fc_val = (Node *) lfirst(fc_lc);

			fc_coltypes = lappend_oid(fc_coltypes, exprType(fc_val));
			fc_coltypmods = lappend_int(fc_coltypmods, exprTypmod(fc_val));
			fc_colcollations = lappend_oid(fc_colcollations, InvalidOid);
		}

		/*
		 * 通常，表达式列表中不能有任何当前级别的Vars，
		 * 因为命名空间是空的……但如果我们在
		 * CREATE RULE内，那么可能会出现NEW/OLD引用。
		 * 在这种情况下，我们必须将VALUES RTE标记为LATERAL。
		 */
		if (list_length(fc_pstate->p_rtable) != 1 &&
			contain_vars_of_level((Node *) fc_exprsLists, 0))
			fc_lateral = true;

		/*
		 * 生成VALUES RTE
		 */
		fc_nsitem = addRangeTableEntryForValues(fc_pstate, fc_exprsLists,
											 fc_coltypes, fc_coltypmods, fc_colcollations,
											 NULL, fc_lateral, true);
		addNSItemToQuery(fc_pstate, fc_nsitem, true, false, false);

		/*
		 * 生成引用RTE的Vars列表
		 */
		fc_exprList = expandNSItemVars(fc_nsitem, 0, -1, NULL);

		/*
		 * 对Vars重新应用目标列规格上的任何间接引用
		 */
		fc_exprList = transformInsertRow(fc_pstate, fc_exprList,
									  fc_stmt->cols,
									  fc_icolumns, fc_attrnos,
									  false);
	}
	else
	{
		/*
		 * 处理带有单个VALUES子列表的INSERT ... VALUES。我们
		 * 分开处理这种情况以提高效率。该子列表被直接计算为
		 * 查询的目标列表，没有VALUES RTE。所以它
		 * 像没有FROM的SELECT一样工作。
		 */
		List	   *fc_valuesLists = fc_selectStmt->valuesLists;

		Assert(list_length(fc_valuesLists) == 1);
		Assert(fc_selectStmt->intoClause == NULL);

		/*
		 * 进行基本的表达式转换（与ROW()表达式相同，但允许
		 * 在顶层进行SetToDefault）
		 */
		fc_exprList = transformExpressionList(fc_pstate,
										   (List *) linitial(fc_valuesLists),
										   EXPR_KIND_VALUES_SINGLE,
										   true);

		/* 准备行以分配给目标表 */
		fc_exprList = transformInsertRow(fc_pstate, fc_exprList,
									  fc_stmt->cols,
									  fc_icolumns, fc_attrnos,
									  false);
	}

	/*
	 * 使用计算出的表达式列表生成查询的目标列表。
	 * 此外，将所有目标列标记为需要插入权限。
	 */
	fc_rte = fc_pstate->p_target_nsitem->p_rte;
	fc_qry->targetList = NIL;
	Assert(list_length(fc_exprList) <= list_length(fc_icolumns));
	forthree(fc_lc, fc_exprList, fc_icols, fc_icolumns, fc_attnos, fc_attrnos)
	{
		Expr	   *fc_expr = (Expr *) lfirst(fc_lc);
		ResTarget  *fc_col = lfirst_node(ResTarget, fc_icols);
		AttrNumber	fc_attr_num = (AttrNumber) lfirst_int(fc_attnos);
		TargetEntry *fc_tle;

		fc_tle = makeTargetEntry(fc_expr,
							  fc_attr_num,
							  fc_col->name,
							  false);
		fc_qry->targetList = lappend(fc_qry->targetList, fc_tle);

		fc_rte->insertedCols = bms_add_member(fc_rte->insertedCols,
										   fc_attr_num - FirstLowInvalidHeapAttributeNumber);
	}

	/*
	 * 如果我们还有任何子句需要处理，将查询命名空间设置为
	 * 仅包含目标关系，删除在子SELECT或VALUES列表中添加的任何条目。
	 */
	if (fc_stmt->onConflictClause || fc_stmt->returningList)
	{
		fc_pstate->p_namespace = NIL;
		addNSItemToQuery(fc_pstate, fc_pstate->p_target_nsitem,
						 false, true, true);
	}

	/* 处理ON CONFLICT（如果有）。 */
	if (fc_stmt->onConflictClause)
		fc_qry->onConflict = fc_transformOnConflictClause(fc_pstate,
													fc_stmt->onConflictClause);

	/* 处理RETURNING（如果有）。 */
	if (fc_stmt->returningList)
		fc_qry->returningList = fc_transformReturningList(fc_pstate,
													fc_stmt->returningList);

	/* 已完成范围表和连接树的构建 */
	fc_qry->rtable = fc_pstate->p_rtable;
	fc_qry->jointree = makeFromExpr(fc_pstate->p_joinlist, NULL);

	fc_qry->hasTargetSRFs = fc_pstate->p_hasTargetSRFs;
	fc_qry->hasSubLinks = fc_pstate->p_hasSubLinks;

	assign_query_collations(fc_pstate, fc_qry);

	return fc_qry;
}

/*
 * 为目标表准备插入行。
 *
 * exprlist：源值的转换表达式；这些可能来自
 * VALUES行，或是引用子SELECT或VALUES RTE输出的Vars。
 * stmtcols：INSERT的原始目标列规范（我们只是测试NIL）
 * icolumns：有效的目标列规范（ResTarget列表）
 * attrnos：整数列号（必须与icolumns长度相同）
 * strip_indirection：如果为真，则移除任何字段/数组赋值节点
 */
List * transformInsertRow(ParseState *fc_pstate, List *fc_exprlist,
				   List *fc_stmtcols, List *fc_icolumns, List *fc_attrnos,
				   bool fc_strip_indirection)
{
	List	   *fc_result;
	ListCell   *fc_lc;
	ListCell   *fc_icols;
	ListCell   *fc_attnos;

	/*
	 * 检查表达式列表的长度。它的表达式数量不能超过
	 * 目标列的数量。我们允许更少的，但仅在没有给出明确的
	 * 列表的情况下（其余列隐式默认化）。注意我们必须在
	 * 转换后检查这一点，因为转换可能会将'*'展开为多个项。
	 */
	if (list_length(fc_exprlist) > list_length(fc_icolumns))
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("INSERT has more expressions than target columns"),
				 parser_errposition(fc_pstate,
									exprLocation(list_nth(fc_exprlist,
														  list_length(fc_icolumns))))));
	if (fc_stmtcols != NIL &&
		list_length(fc_exprlist) < list_length(fc_icolumns))
	{
		/*
		 * 我们可能会在INSERT ... SELECT (a,b,c) FROM ...等情况下到达这里，
		 * 用户偶然创建了一个RowExpr而不是单独的列。如果这似乎是问题，添加
		 * 一个合适的提示，因为主错误消息在这种情况下非常误导。
		 * （如果没有stmtcols，您将会收到有关数据类型
		 * 不匹配的消息，这会相对不那么误导，因此我们在这种情况下无需担心给出提示。）
		 */
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("INSERT has more target columns than expressions"),
				 ((list_length(fc_exprlist) == 1 &&
				   fc_count_rowexpr_columns(fc_pstate, linitial(fc_exprlist)) ==
				   list_length(fc_icolumns)) ?
				  errhint("The insertion source is a row expression containing the same number of columns expected by the INSERT. Did you accidentally use extra parentheses?") : 0),
				 parser_errposition(fc_pstate,
									exprLocation(list_nth(fc_icolumns,
														  list_length(fc_exprlist))))));
	}

	/*
	 * 为目标表准备列。
	 */
	fc_result = NIL;
	forthree(fc_lc, fc_exprlist, fc_icols, fc_icolumns, fc_attnos, fc_attrnos)
	{
		Expr	   *fc_expr = (Expr *) lfirst(fc_lc);
		ResTarget  *fc_col = lfirst_node(ResTarget, fc_icols);
		int			fc_attno = lfirst_int(fc_attnos);

		fc_expr = transformAssignedExpr(fc_pstate, fc_expr,
									 EXPR_KIND_INSERT_TARGET,
									 fc_col->name,
									 fc_attno,
									 fc_col->indirection,
									 fc_col->location);

		if (fc_strip_indirection)
		{
			/*
			 * 我们需要移除顶层字段存储和下标引用，
			 * 以及出现在其中一个上方的任何CoerceToDomain——
			 * 但不是不在其中一个上方的CoerceToDomain。
			 */
			while (fc_expr)
			{
				Expr	   *fc_subexpr = fc_expr;

				while (IsA(fc_subexpr, CoerceToDomain))
				{
					fc_subexpr = ((CoerceToDomain *) fc_subexpr)->arg;
				}
				if (IsA(fc_subexpr, FieldStore))
				{
					FieldStore *fc_fstore = (FieldStore *) fc_subexpr;

					fc_expr = (Expr *) linitial(fc_fstore->newvals);
				}
				else if (IsA(fc_subexpr, SubscriptingRef))
				{
					SubscriptingRef *fc_sbsref = (SubscriptingRef *) fc_subexpr;

					if (fc_sbsref->refassgnexpr == NULL)
						break;

					fc_expr = fc_sbsref->refassgnexpr;
				}
				else
					break;
			}
		}

		fc_result = lappend(fc_result, fc_expr);
	}

	return fc_result;
}

/*
 * transformOnConflictClause -
 *	  转换INSERT中的OnConflictClause
 */
static OnConflictExpr * fc_transformOnConflictClause(ParseState *fc_pstate,
						  OnConflictClause *fc_onConflictClause)
{
	ParseNamespaceItem *fc_exclNSItem = NULL;
	List	   *fc_arbiterElems;
	Node	   *fc_arbiterWhere;
	Oid			fc_arbiterConstraint;
	List	   *fc_onConflictSet = NIL;
	Node	   *fc_onConflictWhere = NULL;
	int			fc_exclRelIndex = 0;
	List	   *fc_exclRelTlist = NIL;
	OnConflictExpr *fc_result;

	/*
	 * 如果这是ON CONFLICT ... UPDATE，首先为EXCLUDED伪关系
	 * 创建范围表条目，以便在处理仲裁表达式时能够存在。
	 * （您实际上不能从那里引用它，但如果尝试
	 * 这样做，这将提供有用的错误消息。）
	 */
	if (fc_onConflictClause->action == ONCONFLICT_UPDATE)
	{
		Relation	fc_targetrel = fc_pstate->p_target_relation;
		RangeTblEntry *fc_exclRte;

		fc_exclNSItem = addRangeTableEntryForRelation(fc_pstate,
												   fc_targetrel,
												   RowExclusiveLock,
												   makeAlias("excluded", NIL),
												   false, false);
		fc_exclRte = fc_exclNSItem->p_rte;
		fc_exclRelIndex = fc_exclNSItem->p_rtindex;

		/*
		 * relkind设置为复合，以表示我们不处理
		 * 实际关系，并且对此不需要进行权限检查。
		 * （我们将检查实际的目标关系。）
		 */
		fc_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
		fc_exclRte->requiredPerms = 0;
		/* exclRte中的其他权限字段已经为空 */

		/* 创建EXCLUDED关系的目标列表以供EXPLAIN使用 */
		fc_exclRelTlist = BuildOnConflictExcludedTargetlist(fc_targetrel,
														 fc_exclRelIndex);
	}

	/* 处理仲裁者子句，冲突时 ON (...) */
	transformOnConflictArbiter(fc_pstate, fc_onConflictClause, &fc_arbiterElems,
							   &fc_arbiterWhere, &fc_arbiterConstraint);

	/* 处理 DO UPDATE */
	if (fc_onConflictClause->action == ONCONFLICT_UPDATE)
	{
		/* 
		 * UPDATE 目标列表中的表达式需要像 UPDATE 一样处理 
		 * 而不是 INSERT。我们不需要保存/恢复，因为所有的 INSERT 
		 * 表达式已经被解析过了。
		 */
		fc_pstate->p_is_insert = false;

		/* 
		 * 将 EXCLUDED 伪关系添加到查询命名空间，使其在 UPDATE 子表达式中可用。
		 */
		addNSItemToQuery(fc_pstate, fc_exclNSItem, false, true, true);

		/* 
		 * 现在转换 UPDATE 子表达式。
		 */
		fc_onConflictSet =
			transformUpdateTargetList(fc_pstate, fc_onConflictClause->targetList);

		fc_onConflictWhere = transformWhereClause(fc_pstate,
											   fc_onConflictClause->whereClause,
											   EXPR_KIND_WHERE, "WHERE");

		/* 
		 * 从查询命名空间中移除 EXCLUDED 伪关系，因为它在 RETURNING 中不应该可用。
		 * （也许有一天我们可以允许这样，并跳过这一步。）
		 */
		Assert((ParseNamespaceItem *) llast(fc_pstate->p_namespace) == fc_exclNSItem);
		fc_pstate->p_namespace = list_delete_last(fc_pstate->p_namespace);
	}

	/* 最后，构建 ON CONFLICT DO [NOTHING | UPDATE] 表达式 */
	fc_result = makeNode(OnConflictExpr);

	fc_result->action = fc_onConflictClause->action;
	fc_result->arbiterElems = fc_arbiterElems;
	fc_result->arbiterWhere = fc_arbiterWhere;
	fc_result->constraint = fc_arbiterConstraint;
	fc_result->onConflictSet = fc_onConflictSet;
	fc_result->onConflictWhere = fc_onConflictWhere;
	fc_result->exclRelIndex = fc_exclRelIndex;
	fc_result->exclRelTlist = fc_exclRelTlist;

	return fc_result;
}


/* 
 * BuildOnConflictExcludedTargetlist
 *		为 ON CONFLICT 的 EXCLUDED 伪关系创建目标列表，
 *		表示的目标关系（targetrel）中的列，varno 为 exclRelIndex。
 *
 * 注意：导出供重写器使用。
 */
List * BuildOnConflictExcludedTargetlist(Relation fc_targetrel,
								  Index fc_exclRelIndex)
{
	List	   *fc_result = NIL;
	int			fc_attno;
	Var		   *fc_var;
	TargetEntry *fc_te;

	/* 
	 * 注意，tlist 的 resnos 必须与底层关系的 attnos 相对应，
	 * 因此我们也需要被删除列的条目。
	 */
	for (fc_attno = 0; fc_attno < RelationGetNumberOfAttributes(fc_targetrel); fc_attno++)
	{
		Form_pg_attribute fc_attr = TupleDescAttr(fc_targetrel->rd_att, fc_attno);
		char	   *fc_name;

		if (fc_attr->attisdropped)
		{
			/* 
			 * 这里不能使用 atttypid，但 Const 声称是什么类型并不重要。
			 */
			fc_var = (Var *) makeNullConst(INT4OID, -1, InvalidOid);
			fc_name = NULL;
		}
		else
		{
			fc_var = makeVar(fc_exclRelIndex, fc_attno + 1,
						  fc_attr->atttypid, fc_attr->atttypmod,
						  fc_attr->attcollation,
						  0);
			fc_name = pstrdup(NameStr(fc_attr->attname));
		}

		fc_te = makeTargetEntry((Expr *) fc_var,
							 fc_attno + 1,
							 fc_name,
							 false);

		fc_result = lappend(fc_result, fc_te);
	}

	/* 
	 * 添加一个整个行的 Var 条目以支持对 "EXCLUDED.*" 的引用。
	 * 该条目的 resno 必须与 Var 的 varattno 匹配，否则在
	 * setrefs.c 中解析引用时会发生错误。这与目标列表的正常约定相悖，
	 * 但没关系，因为我们不把它用作一个真正的目标列表。
	 */
	fc_var = makeVar(fc_exclRelIndex, InvalidAttrNumber,
				  fc_targetrel->rd_rel->reltype,
				  -1, InvalidOid, 0);
	fc_te = makeTargetEntry((Expr *) fc_var, InvalidAttrNumber, NULL, true);
	fc_result = lappend(fc_result, fc_te);

	return fc_result;
}


/* 
 * count_rowexpr_columns -
 *	  获取 ROW() 表达式中包含的列数；
 *	  如果表达式不是 RowExpr 或引用一个的 Var，则返回 -1。
 *
 * 这目前仅用于提示目的，因此我们对于识别所有可能情况并不是非常严格。
 * Var 的情况很有趣，因为那就是我们在 INSERT ... SELECT (...) 的情形中会得到的。
 */
static int fc_count_rowexpr_columns(ParseState *fc_pstate, Node *fc_expr)
{
	if (fc_expr == NULL)
		return -1;
	if (IsA(fc_expr, RowExpr))
		return list_length(((RowExpr *) fc_expr)->args);
	if (IsA(fc_expr, Var))
	{
		Var		   *fc_var = (Var *) fc_expr;
		AttrNumber	fc_attnum = fc_var->varattno;

		if (fc_attnum > 0 && fc_var->vartype == RECORDOID)
		{
			RangeTblEntry *fc_rte;

			fc_rte = GetRTEByRangeTablePosn(fc_pstate, fc_var->varno, fc_var->varlevelsup);
			if (fc_rte->rtekind == RTE_SUBQUERY)
			{
				/* 子查询中的 FROM：检查子选择的输出表达式 */
				TargetEntry *fc_ste = get_tle_by_resno(fc_rte->subquery->targetList,
													fc_attnum);

				if (fc_ste == NULL || fc_ste->resjunk)
					return -1;
				fc_expr = (Node *) fc_ste->expr;
				if (IsA(fc_expr, RowExpr))
					return list_length(((RowExpr *) fc_expr)->args);
			}
		}
	}
	return -1;
}


/* 
 * transformSelectStmt -
 *	  转换一个选择语句
 *
 * 注意：这仅涵盖没有集合操作和没有 VALUES 列表的情况；
 * 见下文了解其他情况。
 */
static Query * fc_transformSelectStmt(ParseState *fc_pstate, SelectStmt *fc_stmt)
{
	Query	   *fc_qry = makeNode(Query);
	Node	   *fc_qual;
	ListCell   *fc_l;

	fc_qry->commandType = CMD_SELECT;

	/* 独立于其他内容处理 WITH 子句 */
	if (fc_stmt->withClause)
	{
		fc_qry->hasRecursive = fc_stmt->withClause->recursive;
		fc_qry->cteList = transformWithClause(fc_pstate, fc_stmt->withClause);
		fc_qry->hasModifyingCTE = fc_pstate->p_hasModifyingCTE;
	}

	/* 如果我们从不允许使用 INTO 的地方被调用，提出投诉 */
	if (fc_stmt->intoClause)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("SELECT ... INTO is not allowed here"),
				 parser_errposition(fc_pstate,
									exprLocation((Node *) fc_stmt->intoClause))));

	/* 使 FOR UPDATE/FOR SHARE 信息可用于 addRangeTableEntry */
	fc_pstate->p_locking_clause = fc_stmt->lockingClause;

	/* 使 WINDOW 信息也可用于窗口函数 */
	fc_pstate->p_windowdefs = fc_stmt->windowClause;

	/* 处理 FROM 子句 */
	transformFromClause(fc_pstate, fc_stmt->fromClause);

	/* 转换目标列表 */
	fc_qry->targetList = transformTargetList(fc_pstate, fc_stmt->targetList,
										  EXPR_KIND_SELECT_TARGET);

	/* 标记列的来源 */
	markTargetListOrigins(fc_pstate, fc_qry->targetList);

	/* 转换 WHERE */
	fc_qual = transformWhereClause(fc_pstate, fc_stmt->whereClause,
								EXPR_KIND_WHERE, "WHERE");

	/* HAVING 子句的初步处理与 WHERE 子句非常相似 */
	fc_qry->havingQual = transformWhereClause(fc_pstate, fc_stmt->havingClause,
										   EXPR_KIND_HAVING, "HAVING");

	/* 
	 * 转换排序/分组内容。首先执行 ORDER BY，因为 transformGroupClause 
	 * 和 transformDistinctClause 都需要结果。注意这些函数也可以改变 
	 * targetList，因此通过引用将其传递给它们。
	 */
	fc_qry->sortClause = transformSortClause(fc_pstate,
										  fc_stmt->sortClause,
										  &fc_qry->targetList,
										  EXPR_KIND_ORDER_BY,
										  false /* 允许 SQL92 规则 */ );

	fc_qry->groupClause = transformGroupClause(fc_pstate,
											fc_stmt->groupClause,
											&fc_qry->groupingSets,
											&fc_qry->targetList,
											fc_qry->sortClause,
											EXPR_KIND_GROUP_BY,
											false /* 允许 SQL92 规则 */ );
	fc_qry->groupDistinct = fc_stmt->groupDistinct;

	if (fc_stmt->distinctClause == NIL)
	{
		fc_qry->distinctClause = NIL;
		fc_qry->hasDistinctOn = false;
	}
	else if (linitial(fc_stmt->distinctClause) == NULL)
	{
		/* 我们有 SELECT DISTINCT */
		fc_qry->distinctClause = transformDistinctClause(fc_pstate,
													  &fc_qry->targetList,
													  fc_qry->sortClause,
													  false);
		fc_qry->hasDistinctOn = false;
	}
	else
	{
		/* 我们有 SELECT DISTINCT ON */
		fc_qry->distinctClause = transformDistinctOnClause(fc_pstate,
														fc_stmt->distinctClause,
														&fc_qry->targetList,
														fc_qry->sortClause);
		fc_qry->hasDistinctOn = true;
	}

	/* 转换 LIMIT */
	fc_qry->limitOffset = transformLimitClause(fc_pstate, fc_stmt->limitOffset,
											EXPR_KIND_OFFSET, "OFFSET",
											fc_stmt->limitOption);
	fc_qry->limitCount = transformLimitClause(fc_pstate, fc_stmt->limitCount,
										   EXPR_KIND_LIMIT, "LIMIT",
										   fc_stmt->limitOption);
	fc_qry->limitOption = fc_stmt->limitOption;

	/* 在查看所有窗口函数之后转换窗口子句 */
	fc_qry->windowClause = transformWindowDefinitions(fc_pstate,
												   fc_pstate->p_windowdefs,
												   &fc_qry->targetList);

	/* 将任何仍未解析的输出列解析为文本类型 */
	if (fc_pstate->p_resolve_unknowns)
		resolveTargetListUnknowns(fc_pstate, fc_qry->targetList);

	fc_qry->rtable = fc_pstate->p_rtable;
	fc_qry->jointree = makeFromExpr(fc_pstate->p_joinlist, fc_qual);

	fc_qry->hasSubLinks = fc_pstate->p_hasSubLinks;
	fc_qry->hasWindowFuncs = fc_pstate->p_hasWindowFuncs;
	fc_qry->hasTargetSRFs = fc_pstate->p_hasTargetSRFs;
	fc_qry->hasAggs = fc_pstate->p_hasAggs;

	foreach(fc_l, fc_stmt->lockingClause)
	{
		fc_transformLockingClause(fc_pstate, fc_qry,
							   (LockingClause *) lfirst(fc_l), false);
	}

	assign_query_collations(fc_pstate, fc_qry);

	/* 必须在排序后完成，以确保可靠的表达式比较 */
	if (fc_pstate->p_hasAggs || fc_qry->groupClause || fc_qry->groupingSets || fc_qry->havingQual)
		parseCheckAggregates(fc_pstate, fc_qry);

	return fc_qry;
}

/* 
 * transformValuesClause -
 *	  转换作为独立 SELECT 使用的 VALUES 子句
 *
 * 我们构建一个包含 VALUES RTE 的 Query，就像写了
 *			SELECT * FROM (VALUES ...) AS "*VALUES*"
 */ 
static Query * fc_transformValuesClause(ParseState *fc_pstate, SelectStmt *fc_stmt)
{
	Query	   *fc_qry = makeNode(Query);
	List	   *fc_exprsLists = NIL;
	List	   *fc_coltypes = NIL;
	List	   *fc_coltypmods = NIL;
	List	   *fc_colcollations = NIL;
	List	  **fc_colexprs = NULL;
	int			fc_sublist_length = -1;
	bool		fc_lateral = false;
	ParseNamespaceItem *fc_nsitem;
	ListCell   *fc_lc;
	ListCell   *fc_lc2;
	int			fc_i;

	fc_qry->commandType = CMD_SELECT;

	/* 大多数 SELECT 内容在 VALUES 子句中不适用 */
	Assert(fc_stmt->distinctClause == NIL);
	Assert(fc_stmt->intoClause == NULL);
	Assert(fc_stmt->targetList == NIL);
	Assert(fc_stmt->fromClause == NIL);
	Assert(fc_stmt->whereClause == NULL);
	Assert(fc_stmt->groupClause == NIL);
	Assert(fc_stmt->havingClause == NULL);
	Assert(fc_stmt->windowClause == NIL);
	Assert(fc_stmt->op == SETOP_NONE);

	/* 独立于其他内容处理 WITH 子句 */
	if (fc_stmt->withClause)
	{
		fc_qry->hasRecursive = fc_stmt->withClause->recursive;
		fc_qry->cteList = transformWithClause(fc_pstate, fc_stmt->withClause);
		fc_qry->hasModifyingCTE = fc_pstate->p_hasModifyingCTE;
	}

	/*
	 * 对于每一行 VALUES，转换原始表达式。
	 *
	 * 请注意，我们构建的中间表示是按列组织的，而不是按行组织的。
	 * 这简化了下面的类型和排序规则处理。
	 */
	foreach(fc_lc, fc_stmt->valuesLists)
	{
		List	   *fc_sublist = (List *) lfirst(fc_lc);

		/*
		 * 执行基本的表达式转换（与 ROW() 表达式相同，但在这里
		 * 我们不允许 SetToDefault）
		 */
		fc_sublist = transformExpressionList(fc_pstate, fc_sublist,
										  EXPR_KIND_VALUES, false);

		/*
		 * 所有子列表在转换后必须具有相同的长度
		 * （转换可能会将 '*' 扩展为多个项）。VALUES RTE 无法
		 * 处理不同的情况。
		 */
		if (fc_sublist_length < 0)
		{
			/* 记住转换后第一个子列表的长度 */
			fc_sublist_length = list_length(fc_sublist);
			/* 为每列列表分配数组 */
			fc_colexprs = (List **) palloc0(fc_sublist_length * sizeof(List *));
		}
		else if (fc_sublist_length != list_length(fc_sublist))
		{
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("VALUES lists must all be the same length"),
					 parser_errposition(fc_pstate,
										exprLocation((Node *) fc_sublist))));
#ifdef FDD //cppcheck
			return NULL;//cppcheck
#endif
		}

		/* 构建每列的表达式列表 */
		fc_i = 0;
		foreach(fc_lc2, fc_sublist)
		{
			Node	   *fc_col = (Node *) lfirst(fc_lc2);

			fc_colexprs[fc_i] = lappend(fc_colexprs[fc_i], fc_col);
			fc_i++;
		}

		/* 释放子列表的单元格以节省内存 */
		list_free(fc_sublist);

		/* 为该行准备一个 exprsLists 元素 */
		fc_exprsLists = lappend(fc_exprsLists, NIL);
	}

	/*
	 * 现在解析列的公共类型，并将所有内容强制转换为
	 * 这些类型。然后识别每列的公共 typmod 和公共 collation（如果有的话）。
	 *
	 * 我们现在必须进行排序规则处理，因为 (1) assign_query_collations
	 * 不处理 rangetable 条目，并且 (2) 我们需要给 VALUES
	 * RTE 标记列排序规则，以便在外部查询中使用。我们不
	 * 将隐式排序规则冲突视为错误；相反，该列将仅显示 InvalidOid 作为其排序规则，如果导致无法解析排序规则，稍后你将会遇到失败。
	 *
	 * 注意，我们就地修改每列的表达式列表。
	 */
	for (fc_i = 0; fc_i < fc_sublist_length; fc_i++)
	{
		Oid			fc_coltype;
		int32		fc_coltypmod;
		Oid			fc_colcoll;

		fc_coltype = select_common_type(fc_pstate, fc_colexprs[fc_i], "VALUES", NULL);

		foreach(fc_lc, fc_colexprs[fc_i])
		{
			Node	   *fc_col = (Node *) lfirst(fc_lc);

			fc_col = coerce_to_common_type(fc_pstate, fc_col, fc_coltype, "VALUES");
			lfirst(fc_lc) = (void *) fc_col;
		}

		fc_coltypmod = select_common_typmod(fc_pstate, fc_colexprs[fc_i], fc_coltype);
		fc_colcoll = select_common_collation(fc_pstate, fc_colexprs[fc_i], true);

		fc_coltypes = lappend_oid(fc_coltypes, fc_coltype);
		fc_coltypmods = lappend_int(fc_coltypmods, fc_coltypmod);
		fc_colcollations = lappend_oid(fc_colcollations, fc_colcoll);
	}

	/*
	 * 最后，将强制转换的表达式重新排列为按行组织的列表。
	 */
	for (fc_i = 0; fc_i < fc_sublist_length; fc_i++)
	{
		forboth(fc_lc, fc_colexprs[fc_i], fc_lc2, fc_exprsLists)
		{
			Node	   *fc_col = (Node *) lfirst(fc_lc);
			List	   *fc_sublist = lfirst(fc_lc2);

			fc_sublist = lappend(fc_sublist, fc_col);
			lfirst(fc_lc2) = fc_sublist;
		}
		list_free(fc_colexprs[fc_i]);
	}

	/*
	 * 通常，表达式列表中不能有任何当前级别的 Vars，因为命名空间是空的...
	 * 但如果我们在 CREATE RULE 中，那么可能会出现 NEW/OLD 引用。在这种情况下，
	 * 我们必须将 VALUES RTE 标记为 LATERAL。
	 */
	if (fc_pstate->p_rtable != NIL &&
		contain_vars_of_level((Node *) fc_exprsLists, 0))
		fc_lateral = true;

	/*
	 * 生成 VALUES RTE
	 */
	fc_nsitem = addRangeTableEntryForValues(fc_pstate, fc_exprsLists,
										 fc_coltypes, fc_coltypmods, fc_colcollations,
										 NULL, fc_lateral, true);
	addNSItemToQuery(fc_pstate, fc_nsitem, true, true, true);

	/*
	 * 生成一个目标列表，仿佛展开 "*"
	 */
	Assert(fc_pstate->p_next_resno == 1);
	fc_qry->targetList = expandNSItemAttrs(fc_pstate, fc_nsitem, 0, true, -1);

	/*
	 * 语法允许将 ORDER BY、LIMIT 和 FOR UPDATE 附加到
	 * VALUES，所以要处理它。
	 */
	fc_qry->sortClause = transformSortClause(fc_pstate,
										  fc_stmt->sortClause,
										  &fc_qry->targetList,
										  EXPR_KIND_ORDER_BY,
										  false /* 允许 SQL92 规则 */ );

	fc_qry->limitOffset = transformLimitClause(fc_pstate, fc_stmt->limitOffset,
											EXPR_KIND_OFFSET, "OFFSET",
											fc_stmt->limitOption);
	fc_qry->limitCount = transformLimitClause(fc_pstate, fc_stmt->limitCount,
										   EXPR_KIND_LIMIT, "LIMIT",
										   fc_stmt->limitOption);
	fc_qry->limitOption = fc_stmt->limitOption;

	if (fc_stmt->lockingClause)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("%s cannot be applied to VALUES",
						LCS_asString(((LockingClause *)
									  linitial(fc_stmt->lockingClause))->strength))));

	fc_qry->rtable = fc_pstate->p_rtable;
	fc_qry->jointree = makeFromExpr(fc_pstate->p_joinlist, NULL);

	fc_qry->hasSubLinks = fc_pstate->p_hasSubLinks;

	assign_query_collations(fc_pstate, fc_qry);

	return fc_qry;
}

/*
 * transformSetOperationStmt -
 *	  转换一个集合操作树
 *
 * 一个集合操作树就是一个 SELECT，但具有 UNION/INTERSECT/EXCEPT
 * 结构。我们必须转换每个叶子 SELECT 并构建一个顶级
 * 查询，该查询包含叶子 SELECT 作为其 rangetable 中的子查询。
 * 集合操作的树被转换为顶级查询的 setOperations 字段。
 */
static Query * fc_transformSetOperationStmt(ParseState *fc_pstate, SelectStmt *fc_stmt)
{
	Query	   *fc_qry = makeNode(Query);
	SelectStmt *fc_leftmostSelect;
	int			fc_leftmostRTI;
	Query	   *fc_leftmostQuery;
	SetOperationStmt *fc_sostmt;
	List	   *fc_sortClause;
	Node	   *fc_limitOffset;
	Node	   *fc_limitCount;
	List	   *fc_lockingClause;
	WithClause *fc_withClause;
	Node	   *fc_node;
	ListCell   *fc_left_tlist,
			   *fc_lct,
			   *fc_lcm,
			   *fc_lcc,
			   *fc_l;
	List	   *fc_targetvars,
			   *fc_targetnames,
			   *fc_sv_namespace;
	int			fc_sv_rtable_length;
	ParseNamespaceItem *fc_jnsitem;
	ParseNamespaceColumn *fc_sortnscolumns;
	int			fc_sortcolindex;
	int			fc_tllen;

	fc_qry->commandType = CMD_SELECT;

	/*
	 * 找到最左边的叶节点 SelectStmt。我们目前只需要这样做
	 * 以便在那里提供一个合适的错误消息，如果存在 INTO 子句，
	 * 意味着集合操作树处于不允许 INTO 的上下文中。
	 * （transformSetOperationTree 无论如何会抛出错误，但
	 * 似乎值得为非最左边的 INTO 抛出不同的错误，因此我们
	 * 在 transformSetOperationTree 中生成该错误。）
	 */
	fc_leftmostSelect = fc_stmt->larg;
	while (fc_leftmostSelect && fc_leftmostSelect->op != SETOP_NONE)
		fc_leftmostSelect = fc_leftmostSelect->larg;
	Assert(fc_leftmostSelect && IsA(fc_leftmostSelect, SelectStmt) &&
		   fc_leftmostSelect->larg == NULL);
	if (fc_leftmostSelect->intoClause)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("SELECT ... INTO is not allowed here"),
				 parser_errposition(fc_pstate,
									exprLocation((Node *) fc_leftmostSelect->intoClause))));

	/*
	 * 我们需要在这里提取 ORDER BY 和其他顶级子句，而不
	 * 让 transformSetOperationTree() 看到它们——否则它会
	 * 直接递归回到这里！
	 */
	fc_sortClause = fc_stmt->sortClause;
	fc_limitOffset = fc_stmt->limitOffset;
	fc_limitCount = fc_stmt->limitCount;
	fc_lockingClause = fc_stmt->lockingClause;
	fc_withClause = fc_stmt->withClause;

	fc_stmt->sortClause = NIL;
	fc_stmt->limitOffset = NULL;
	fc_stmt->limitCount = NULL;
	fc_stmt->lockingClause = NIL;
	fc_stmt->withClause = NULL;

	/* 目前我们不支持 SET 操作的 FOR UPDATE/SHARE。 */
	if (fc_lockingClause)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
						LCS_asString(((LockingClause *)
									  linitial(fc_lockingClause))->strength))));

	/* 独立处理 WITH 子句 */
	if (fc_withClause)
	{
		fc_qry->hasRecursive = fc_withClause->recursive;
		fc_qry->cteList = transformWithClause(fc_pstate, fc_withClause);
		fc_qry->hasModifyingCTE = fc_pstate->p_hasModifyingCTE;
	}

	/*
	 * 递归转换树的组件。
	 */
	fc_sostmt = castNode(SetOperationStmt,
					  fc_transformSetOperationTree(fc_pstate, fc_stmt, true, NULL));
	Assert(fc_sostmt);
	fc_qry->setOperations = (Node *) fc_sostmt;

	/*
	 * 重新找到最左边的 SELECT（现在它在 rangetable 中是一个子查询）
	 */
	fc_node = fc_sostmt->larg;
	while (fc_node && IsA(fc_node, SetOperationStmt))
		fc_node = ((SetOperationStmt *) fc_node)->larg;
	Assert(fc_node && IsA(fc_node, RangeTblRef));
	fc_leftmostRTI = ((RangeTblRef *) fc_node)->rtindex;
	fc_leftmostQuery = rt_fetch(fc_leftmostRTI, fc_pstate->p_rtable)->subquery;
	Assert(fc_leftmostQuery != NULL);

	/*
	 * 使用最左侧选择的列名和最上面集合操作的公共数据类型/排序规则
	 * 为外部查询生成虚拟目标列表。还为解析 ORDER BY 使用
	 * 虚拟变量及其名称制作列表。
	 *
	 * 注意：我们使用 leftmostRTI 作为虚拟变量的 varno。它
	 * 不应该太过于在乎它们的 RT 索引，只要它们具有与实际 RT 条目对应的索引；否则，如果树在规则重写下被混合，可能会发生奇怪的事情。
	 */
	fc_qry->targetList = NIL;
	fc_targetvars = NIL;
	fc_targetnames = NIL;
	fc_sortnscolumns = (ParseNamespaceColumn *)
		palloc0(list_length(fc_sostmt->colTypes) * sizeof(ParseNamespaceColumn));
	fc_sortcolindex = 0;

	forfour(fc_lct, fc_sostmt->colTypes,
			fc_lcm, fc_sostmt->colTypmods,
			fc_lcc, fc_sostmt->colCollations,
			fc_left_tlist, fc_leftmostQuery->targetList)
	{
		Oid			fc_colType = lfirst_oid(fc_lct);
		int32		fc_colTypmod = lfirst_int(fc_lcm);
		Oid			fc_colCollation = lfirst_oid(fc_lcc);
		TargetEntry *fc_lefttle = (TargetEntry *) lfirst(fc_left_tlist);
		char	   *fc_colName;
		TargetEntry *fc_tle;
		Var		   *fc_var;

		Assert(!fc_lefttle->resjunk);
		fc_colName = pstrdup(fc_lefttle->resname);
		fc_var = makeVar(fc_leftmostRTI,
					  fc_lefttle->resno,
					  fc_colType,
					  fc_colTypmod,
					  fc_colCollation,
					  0);
		fc_var->location = exprLocation((Node *) fc_lefttle->expr);
		fc_tle = makeTargetEntry((Expr *) fc_var,
							  (AttrNumber) fc_pstate->p_next_resno++,
							  fc_colName,
							  false);
		fc_qry->targetList = lappend(fc_qry->targetList, fc_tle);
		fc_targetvars = lappend(fc_targetvars, fc_var);
		fc_targetnames = lappend(fc_targetnames, makeString(fc_colName));
		fc_sortnscolumns[fc_sortcolindex].p_varno = fc_leftmostRTI;
		fc_sortnscolumns[fc_sortcolindex].p_varattno = fc_lefttle->resno;
		fc_sortnscolumns[fc_sortcolindex].p_vartype = fc_colType;
		fc_sortnscolumns[fc_sortcolindex].p_vartypmod = fc_colTypmod;
		fc_sortnscolumns[fc_sortcolindex].p_varcollid = fc_colCollation;
		fc_sortnscolumns[fc_sortcolindex].p_varnosyn = fc_leftmostRTI;
		fc_sortnscolumns[fc_sortcolindex].p_varattnosyn = fc_lefttle->resno;
		fc_sortcolindex++;
	}

	/*
	 * 作为支持使用输出列的表达式的排序子句的第一步，
	 * 生成一个命名空间条目，使输出列可见。将 Join RTE 节点用作
	 * 这个很方便，因为我们可以轻松控制匹配时生成的变量。
	 *
	 * 注意：我们尚未对这种情况做任何有用的事情，但至少
	 * “ORDER BY upper(foo)”将显示正确的错误信息，而不是
	 * “foo not found”。
	 */
	fc_sv_rtable_length = list_length(fc_pstate->p_rtable);

	fc_jnsitem = addRangeTableEntryForJoin(fc_pstate,
										fc_targetnames,
										fc_sortnscolumns,
										JOIN_INNER,
										0,
										fc_targetvars,
										NIL,
										NIL,
										NULL,
										NULL,
										false);

	fc_sv_namespace = fc_pstate->p_namespace;
	fc_pstate->p_namespace = NIL;

	/* 只将 jnsitem 添加到列命名空间 */
	addNSItemToQuery(fc_pstate, fc_jnsitem, false, false, true);

	/*
	 * 目前，我们不支持在集合操作树的输出上使用 resjunk 排序子句——
	 * 你只能使用 SQL92 规范选项，通过名称或编号选择输出列。
	 * 通过检查 transformSortClause 是否没有向 tlist 添加任何项目来强制执行。
	 */
	fc_tllen = list_length(fc_qry->targetList);

	fc_qry->sortClause = transformSortClause(fc_pstate,
										  fc_sortClause,
										  &fc_qry->targetList,
										  EXPR_KIND_ORDER_BY,
										  false /* 允许 SQL92 规则 */ );

	/* 恢复命名空间，从 rtable 中移除 join RTE */
	fc_pstate->p_namespace = fc_sv_namespace;
	fc_pstate->p_rtable = list_truncate(fc_pstate->p_rtable, fc_sv_rtable_length);

	if (fc_tllen != list_length(fc_qry->targetList))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("invalid UNION/INTERSECT/EXCEPT ORDER BY clause"),
				 errdetail("Only result column names can be used, not expressions or functions."),
				 errhint("Add the expression/function to every SELECT, or move the UNION into a FROM clause."),
				 parser_errposition(fc_pstate,
									exprLocation(list_nth(fc_qry->targetList, fc_tllen)))));

	fc_qry->limitOffset = transformLimitClause(fc_pstate, fc_limitOffset,
											EXPR_KIND_OFFSET, "OFFSET",
											fc_stmt->limitOption);
	fc_qry->limitCount = transformLimitClause(fc_pstate, fc_limitCount,
										   EXPR_KIND_LIMIT, "LIMIT",
										   fc_stmt->limitOption);
	fc_qry->limitOption = fc_stmt->limitOption;

	fc_qry->rtable = fc_pstate->p_rtable;
	fc_qry->jointree = makeFromExpr(fc_pstate->p_joinlist, NULL);

	fc_qry->hasSubLinks = fc_pstate->p_hasSubLinks;
	fc_qry->hasWindowFuncs = fc_pstate->p_hasWindowFuncs;
	fc_qry->hasTargetSRFs = fc_pstate->p_hasTargetSRFs;
	fc_qry->hasAggs = fc_pstate->p_hasAggs;

	foreach(fc_l, fc_lockingClause)
	{
		fc_transformLockingClause(fc_pstate, fc_qry,
							   (LockingClause *) lfirst(fc_l), false);
	}

	assign_query_collations(fc_pstate, fc_qry);

	/* 必须在排序后完成，以确保可靠的表达式比较 */
	if (fc_pstate->p_hasAggs || fc_qry->groupClause || fc_qry->groupingSets || fc_qry->havingQual)
		parseCheckAggregates(fc_pstate, fc_qry);

	return fc_qry;
}


/*
 * 为 SetOperationStmt 的 groupClauses 创建一个 SortGroupClause 节点
 *
 * 如果 require_hash 为真，调用者表示他们需要哈希支持，否则将失败。因此，要特别仔细地寻找哈希支持。
 */
SortGroupClause * makeSortGroupClauseForSetOp(Oid fc_rescoltype, bool fc_require_hash)
{
	SortGroupClause *fc_grpcl = makeNode(SortGroupClause);
	Oid			fc_sortop;
	Oid			fc_eqop;
	bool		fc_hashable;

	/* 确定 eqop 和可选的 sortop */
	get_sort_group_operators(fc_rescoltype,
							 false, true, false,
							 &fc_sortop, &fc_eqop, NULL,
							 &fc_hashable);

	/*
	 * 类型缓存不认为记录是可哈希的（请参见 cache_record_field_properties()），但如果调用者确实需要哈希支持，我们可以假设它是可哈希的。最坏的情况是，如果记录的任何组件不支持哈希，我们将在执行时失败。
	 */
	if (fc_require_hash && (fc_rescoltype == RECORDOID || fc_rescoltype == RECORDARRAYOID))
		fc_hashable = true;

	/* 我们还没有 tlist，因此无法分配 sortgrouprefs */
	fc_grpcl->tleSortGroupRef = 0;
	fc_grpcl->eqop = fc_eqop;
	fc_grpcl->sortop = fc_sortop;
	fc_grpcl->nulls_first = false; /* 有或没有 sortop 都可以 */
	fc_grpcl->hashable = fc_hashable;

	return fc_grpcl;
}

/*
 * transformSetOperationTree
 *		递归转换集合操作树的叶子和内部节点
 *
 * 除了返回被转换的节点，如果 targetlist 不是 NULL，我们还返回
 * 其非负担目标条目的列表。对于叶子集合操作节点，这些是实际的
 * 目标条目；否则，它们是为了携带每个输出列的类型、typmod、排序规则
 * 和位置（用于错误消息）而创建的虚拟条目。此信息仅在此函数的内部
 * 递归期间需要，因此外部调用者将 targetlist 传递为 NULL。
 * 注意：传递叶节点的实际目标条目的原因是为了让上层可以将
 * UNKNOWN 常量替换为适当强制转换的常量。
 */
static Node * fc_transformSetOperationTree(ParseState *fc_pstate, SelectStmt *fc_stmt,
						  bool fc_isTopLevel, List **fc_targetlist)
{
	bool		fc_isLeaf;

	Assert(fc_stmt && IsA(fc_stmt, SelectStmt));

	/* 防止由于过于复杂的集合表达式导致的栈溢出 */
	check_stack_depth();

	/*
	 * 对叶子和内部 SELECT 进行不允许操作的有效性检查。
	 */
	if (fc_stmt->intoClause)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"),
				 parser_errposition(fc_pstate,
									exprLocation((Node *) fc_stmt->intoClause))));

	/* 目前我们不支持 SET 操作的 FOR UPDATE/SHARE。 */
	if (fc_stmt->lockingClause)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
						LCS_asString(((LockingClause *)
									  linitial(fc_stmt->lockingClause))->strength))));

	/*
	 * 如果集合操作树的内部节点附带 ORDER BY、LIMIT、FOR UPDATE，
	 * 或 WITH 子句，我们需要将其视为叶子节点以生成独立的子查询树。
	 * 否则，它可以通过父查询下的 SetOperationStmt 节点表示。
	 */
	if (fc_stmt->op == SETOP_NONE)
	{
		Assert(fc_stmt->larg == NULL && fc_stmt->rarg == NULL);
		fc_isLeaf = true;
	}
	else
	{
		Assert(fc_stmt->larg != NULL && fc_stmt->rarg != NULL);
		if (fc_stmt->sortClause || fc_stmt->limitOffset || fc_stmt->limitCount ||
			fc_stmt->lockingClause || fc_stmt->withClause)
			fc_isLeaf = true;
		else
			fc_isLeaf = false;
	}

	if (fc_isLeaf)
	{
		/* 处理叶子 SELECT */
		Query	   *fc_selectQuery;
		char		fc_selectName[32];
		ParseNamespaceItem *fc_nsitem;
		RangeTblRef *fc_rtr;
		ListCell   *fc_tl;

		/*
		 * 将 SelectStmt 转换为查询。
		 *
		 * 这与 SELECT 转换的正常工作相同，除了我们防止将未知类型的输出
		 * 解析为 TEXT。这样并不会改变子查询的语义，因为如果列类型在语义上
		 * 重要，它本来就会被解析为其他类型。这样做让我们可以使用
		 * select_common_type() 来解析这样的输出。
		 *
		 * 注意：先前转换的子查询不会影响此子查询的解析，
		 * 因为它们不在顶层 pstate 的命名空间列表中。
		 */
		fc_selectQuery = parse_sub_analyze((Node *) fc_stmt, fc_pstate,
										NULL, false, false);

		/*
		 * 检查对当前查询级别变量的虚假引用（但上层引用是可以的）。
		 * 通常这不可能发生，因为命名空间将是空的，
		 * 但如果我们在一个规则内部，这可能会发生。
		 */
		if (fc_pstate->p_namespace)
		{
			if (contain_vars_of_level((Node *) fc_selectQuery, 1))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
						 errmsg("UNION/INTERSECT/EXCEPT member statement cannot refer to other relations of same query level"),
						 parser_errposition(fc_pstate,
											locate_var_of_level((Node *) fc_selectQuery, 1))));
		}

		/*
		 * 提取一个非垃圾 TLE 的列表以供上层处理。
		 */
		if (fc_targetlist)
		{
			*fc_targetlist = NIL;
			foreach(fc_tl, fc_selectQuery->targetList)
			{
				TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_tl);

				if (!fc_tle->resjunk)
					*fc_targetlist = lappend(*fc_targetlist, fc_tle);
			}
		}

		/*
		 * 使叶子查询成为顶层范围表中的子查询。
		 */
		snprintf(fc_selectName, sizeof(fc_selectName), "*SELECT* %d",
				 list_length(fc_pstate->p_rtable) + 1);
		fc_nsitem = addRangeTableEntryForSubquery(fc_pstate,
											   fc_selectQuery,
											   makeAlias(fc_selectName, NIL),
											   false,
											   false);

		/*
		 * 返回一个 RangeTblRef 以替换集合操作树中的 SelectStmt。
		 */
		fc_rtr = makeNode(RangeTblRef);
		fc_rtr->rtindex = fc_nsitem->p_rtindex;
		return (Node *) fc_rtr;
	}
	else
	{
		/* 处理内部节点（集合操作节点） */
		SetOperationStmt *fc_op = makeNode(SetOperationStmt);
		List	   *fc_ltargetlist;
		List	   *fc_rtargetlist;
		ListCell   *fc_ltl;
		ListCell   *fc_rtl;
		const char *fc_context;
		bool		fc_recursive = (fc_pstate->p_parent_cte &&
								 fc_pstate->p_parent_cte->cterecursive);

		fc_context = (fc_stmt->op == SETOP_UNION ? "UNION" :
				   (fc_stmt->op == SETOP_INTERSECT ? "INTERSECT" :
					"EXCEPT"));

		fc_op->op = fc_stmt->op;
		fc_op->all = fc_stmt->all;

		/*
		 * 递归转换左子节点。
		 */
		fc_op->larg = fc_transformSetOperationTree(fc_pstate, fc_stmt->larg,
											 false,
											 &fc_ltargetlist);

		/*
		 * 如果我们正在处理一个递归联合查询，现在是检查
		 * 非递归项的输出列并标记包含 CTE 具有这些结果列的时候。
		 * 当然，我们只应在 CTE 的最顶层集合操作处进行此操作。
		 */
		if (fc_isTopLevel && fc_recursive)
			fc_determineRecursiveColTypes(fc_pstate, fc_op->larg, fc_ltargetlist);

		/*
		 * 递归转换右子节点。
		 */
		fc_op->rarg = fc_transformSetOperationTree(fc_pstate, fc_stmt->rarg,
											 false,
											 &fc_rtargetlist);

		/*
		 * 验证两个孩子的非垃圾列具有相同的数量，并确定合并的输出列的类型。
		 */
		if (list_length(fc_ltargetlist) != list_length(fc_rtargetlist))
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("each %s query must have the same number of columns",
							fc_context),
					 parser_errposition(fc_pstate,
										exprLocation((Node *) fc_rtargetlist))));

		if (fc_targetlist)
			*fc_targetlist = NIL;
		fc_op->colTypes = NIL;
		fc_op->colTypmods = NIL;
		fc_op->colCollations = NIL;
		fc_op->groupClauses = NIL;
		forboth(fc_ltl, fc_ltargetlist, fc_rtl, fc_rtargetlist)
		{
			TargetEntry *fc_ltle = (TargetEntry *) lfirst(fc_ltl);
			TargetEntry *fc_rtle = (TargetEntry *) lfirst(fc_rtl);
			Node	   *fc_lcolnode = (Node *) fc_ltle->expr;
			Node	   *fc_rcolnode = (Node *) fc_rtle->expr;
			Oid			fc_lcoltype = exprType(fc_lcolnode);
			Oid			fc_rcoltype = exprType(fc_rcolnode);
			Node	   *fc_bestexpr;
			int			fc_bestlocation;
			Oid			fc_rescoltype;
			int32		fc_rescoltypmod;
			Oid			fc_rescolcoll;

			/* 选择公共类型，与 CASE 等同 */
			fc_rescoltype = select_common_type(fc_pstate,
											list_make2(fc_lcolnode, fc_rcolnode),
											fc_context,
											&fc_bestexpr);
			fc_bestlocation = exprLocation(fc_bestexpr);

			
/*
			 * 验证强制转换实际上是否可能。如果不可能，我们可能会在后面失败，
			 * 但我们希望现在就失败，因为我们有足够的上下文来产生错误光标位置。
			 *
			 * 对于所有非UNKNOWN类型的情况，我们验证可强制转换性，但不修改子表达式，
			 * 以免改变子查询的语义。
			 *
			 * 如果子表达式是UNKNOWN类型的Const或Param，我们希望将其替换为强制转换后的表达式。
			 * 这只能在子节点是叶子集合操作节点时发生。替换表达式是安全的，因为如果子查询的语义
			 * 依赖于这个输出列的类型，它早就将UNKNOWN转换为其他类型了。我们希望这样做是因为 
			 * (a) 我们希望验证Const对于目标类型是否有效，或解析UNKNOWN Param的实际类型，
			 * 以及(b) 我们希望避免子查询的输出类型和解析的目标类型之间的不必要差异。
			 * 这种差异会在规划器中禁用优化。
			 *
			 * 如果是其他UNKNOWN类型节点，例如Var，我们不做任何操作
			 * （知道coerce_to_common_type会失败）。规划器有时能够在
			 * 必须强制转换类型之前将UNKNOWN Var折叠为常量，所以现在失败只会破坏
			 * 可能有效的情况。
			 */
			if (fc_lcoltype != UNKNOWNOID)
				fc_lcolnode = coerce_to_common_type(fc_pstate, fc_lcolnode,
												 fc_rescoltype, fc_context);
			else if (IsA(fc_lcolnode, Const) ||
					 IsA(fc_lcolnode, Param))
			{
				fc_lcolnode = coerce_to_common_type(fc_pstate, fc_lcolnode,
												 fc_rescoltype, fc_context);
				fc_ltle->expr = (Expr *) fc_lcolnode;
			}

			if (fc_rcoltype != UNKNOWNOID)
				fc_rcolnode = coerce_to_common_type(fc_pstate, fc_rcolnode,
												 fc_rescoltype, fc_context);
			else if (IsA(fc_rcolnode, Const) ||
					 IsA(fc_rcolnode, Param))
			{
				fc_rcolnode = coerce_to_common_type(fc_pstate, fc_rcolnode,
												 fc_rescoltype, fc_context);
				fc_rtle->expr = (Expr *) fc_rcolnode;
			}

			fc_rescoltypmod = select_common_typmod(fc_pstate,
												list_make2(fc_lcolnode, fc_rcolnode),
												fc_rescoltype);

			/*
			 * 选择公共排序规则。所有集合操作符都需要公共排序规则，除了UNION ALL；
			 * 参见SQL:2008 7.13 <query expression> 语法规则15c。
			 * （如果我们无法为UNION ALL列识别公共排序规则，colCollations元素
			 * 将被设置为InvalidOid，这可能导致在更高查询级别想要使用该列的
			 * 排序规则时出现运行时错误。）
			 */
			fc_rescolcoll = select_common_collation(fc_pstate,
												 list_make2(fc_lcolnode, fc_rcolnode),
												 (fc_op->op == SETOP_UNION && fc_op->all));

			/* 发出结果 */
			fc_op->colTypes = lappend_oid(fc_op->colTypes, fc_rescoltype);
			fc_op->colTypmods = lappend_int(fc_op->colTypmods, fc_rescoltypmod);
			fc_op->colCollations = lappend_oid(fc_op->colCollations, fc_rescolcoll);

			/*
			 * 对于除UNION ALL以外的所有情况，确定将用于消除重复项的分组操作符
			 * （如果可用，排序操作符也是如此）。
			 */
			if (fc_op->op != SETOP_UNION || !fc_op->all)
			{
				ParseCallbackState fc_pcbstate;

				setup_parser_errposition_callback(&fc_pcbstate, fc_pstate,
												  fc_bestlocation);

				/*
				 * 如果是递归联合，我们需要要求哈希支持。
				 */
				fc_op->groupClauses = lappend(fc_op->groupClauses,
										   makeSortGroupClauseForSetOp(fc_rescoltype, fc_recursive));

				cancel_parser_errposition_callback(&fc_pcbstate);
			}

			/*
			 * 构造一个虚拟的tlist条目以返回。我们使用SetToDefault
			 * 节点作为表达式，因为它正好携带所需的字段，但任何其他表达式节点类型也可以。
			 */
			if (fc_targetlist)
			{
				SetToDefault *fc_rescolnode = makeNode(SetToDefault);
				TargetEntry *fc_restle;

				fc_rescolnode->typeId = fc_rescoltype;
				fc_rescolnode->typeMod = fc_rescoltypmod;
				fc_rescolnode->collation = fc_rescolcoll;
				fc_rescolnode->location = fc_bestlocation;
				fc_restle = makeTargetEntry((Expr *) fc_rescolnode,
										 0, /* 无需设置resno */
										 NULL,
										 false);
				*fc_targetlist = lappend(*fc_targetlist, fc_restle);
			}
		}

		return (Node *) fc_op;
	}
}

/*
 * 处理递归联合的非递归项的输出
 * 以设置父CTE的列
 */
static void fc_determineRecursiveColTypes(ParseState *fc_pstate, Node *fc_larg, List *fc_nrtargetlist)
{
	Node	   *fc_node;
	int			fc_leftmostRTI;
	Query	   *fc_leftmostQuery;
	List	   *fc_targetList;
	ListCell   *fc_left_tlist;
	ListCell   *fc_nrtl;
	int			fc_next_resno;

	/*
	 * 找到最左侧的叶子SELECT
	 */
	fc_node = fc_larg;
	while (fc_node && IsA(fc_node, SetOperationStmt))
		fc_node = ((SetOperationStmt *) fc_node)->larg;
	Assert(fc_node && IsA(fc_node, RangeTblRef));
	fc_leftmostRTI = ((RangeTblRef *) fc_node)->rtindex;
	fc_leftmostQuery = rt_fetch(fc_leftmostRTI, fc_pstate->p_rtable)->subquery;
	Assert(fc_leftmostQuery != NULL);

	/*
	 * 使用最左侧选择的列名和非递归项的虚拟结果表达式生成虚拟目标列表。
	 */
	fc_targetList = NIL;
	fc_next_resno = 1;

	forboth(fc_nrtl, fc_nrtargetlist, fc_left_tlist, fc_leftmostQuery->targetList)
	{
		TargetEntry *fc_nrtle = (TargetEntry *) lfirst(fc_nrtl);
		TargetEntry *fc_lefttle = (TargetEntry *) lfirst(fc_left_tlist);
		char	   *fc_colName;
		TargetEntry *fc_tle;

		Assert(!fc_lefttle->resjunk);
		fc_colName = pstrdup(fc_lefttle->resname);
		fc_tle = makeTargetEntry(fc_nrtle->expr,
							  fc_next_resno++,
							  fc_colName,
							  false);
		fc_targetList = lappend(fc_targetList, fc_tle);
	}

	/* 现在使用虚拟目标列表构建 CTE 的输出列信息 */
	analyzeCTETargetList(fc_pstate, fc_pstate->p_parent_cte, fc_targetList);
}


/*
 * transformReturnStmt -
 *	  转换返回语句
 */
static Query * fc_transformReturnStmt(ParseState *fc_pstate, ReturnStmt *fc_stmt)
{
	Query	   *fc_qry = makeNode(Query);

	fc_qry->commandType = CMD_SELECT;
	fc_qry->isReturn = true;

	fc_qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(fc_pstate, fc_stmt->returnval, EXPR_KIND_SELECT_TARGET),
												 1, NULL, false));

	if (fc_pstate->p_resolve_unknowns)
		resolveTargetListUnknowns(fc_pstate, fc_qry->targetList);
	fc_qry->rtable = fc_pstate->p_rtable;
	fc_qry->jointree = makeFromExpr(fc_pstate->p_joinlist, NULL);
	fc_qry->hasSubLinks = fc_pstate->p_hasSubLinks;
	fc_qry->hasWindowFuncs = fc_pstate->p_hasWindowFuncs;
	fc_qry->hasTargetSRFs = fc_pstate->p_hasTargetSRFs;
	fc_qry->hasAggs = fc_pstate->p_hasAggs;

	assign_query_collations(fc_pstate, fc_qry);

	return fc_qry;
}


/*
 * transformUpdateStmt -
 *	  转换更新语句
 */
static Query * fc_transformUpdateStmt(ParseState *fc_pstate, UpdateStmt *fc_stmt)
{
	Query	   *fc_qry = makeNode(Query);
	ParseNamespaceItem *fc_nsitem;
	Node	   *fc_qual;

	fc_qry->commandType = CMD_UPDATE;
	fc_pstate->p_is_insert = false;

	/* 独立于其他内容处理 WITH 子句 */
	if (fc_stmt->withClause)
	{
		fc_qry->hasRecursive = fc_stmt->withClause->recursive;
		fc_qry->cteList = transformWithClause(fc_pstate, fc_stmt->withClause);
		fc_qry->hasModifyingCTE = fc_pstate->p_hasModifyingCTE;
	}

	fc_qry->resultRelation = setTargetTable(fc_pstate, fc_stmt->relation,
										 fc_stmt->relation->inh,
										 true,
										 ACL_UPDATE);
	fc_nsitem = fc_pstate->p_target_nsitem;

	/* FROM 中的子查询无法访问结果关系 */
	fc_nsitem->p_lateral_only = true;
	fc_nsitem->p_lateral_ok = false;

	/*
	 * FROM 子句是非标准 SQL 语法。我们过去可以通过
	 * REPLACE 在 POSTQUEL 中做到这一点，因此我们保留这一功能。
	 */
	transformFromClause(fc_pstate, fc_stmt->fromClause);

	/* 剩余的子句可以正常引用结果关系 */
	fc_nsitem->p_lateral_only = false;
	fc_nsitem->p_lateral_ok = true;

	fc_qual = transformWhereClause(fc_pstate, fc_stmt->whereClause,
								EXPR_KIND_WHERE, "WHERE");

	fc_qry->returningList = fc_transformReturningList(fc_pstate, fc_stmt->returningList);

	/*
	 * 现在我们已经完成了类似 SELECT 的处理，可以继续
	 * 转换目标列表以匹配 UPDATE 目标列。
	 */
	fc_qry->targetList = transformUpdateTargetList(fc_pstate, fc_stmt->targetList);

	fc_qry->rtable = fc_pstate->p_rtable;
	fc_qry->jointree = makeFromExpr(fc_pstate->p_joinlist, fc_qual);

	fc_qry->hasTargetSRFs = fc_pstate->p_hasTargetSRFs;
	fc_qry->hasSubLinks = fc_pstate->p_hasSubLinks;

	assign_query_collations(fc_pstate, fc_qry);

	return fc_qry;
}

/*
 * transformUpdateTargetList -
 *	处理 UPDATE/MERGE/INSERT ... ON CONFLICT UPDATE 中的 SET 子句
 */
List * transformUpdateTargetList(ParseState *fc_pstate, List *fc_origTlist)
{
	List	   *fc_tlist = NIL;
	RangeTblEntry *fc_target_rte;
	ListCell   *fc_orig_tl;
	ListCell   *fc_tl;

	fc_tlist = transformTargetList(fc_pstate, fc_origTlist,
								EXPR_KIND_UPDATE_SOURCE);

	/* 为非冲突的 resnos 准备分配给 resjunk 属性 */
	if (fc_pstate->p_next_resno <= RelationGetNumberOfAttributes(fc_pstate->p_target_relation))
		fc_pstate->p_next_resno = RelationGetNumberOfAttributes(fc_pstate->p_target_relation) + 1;

	/* 准备将非垃圾列分配给目标表 */
	fc_target_rte = fc_pstate->p_target_nsitem->p_rte;
	fc_orig_tl = list_head(fc_origTlist);

	foreach(fc_tl, fc_tlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_tl);
		ResTarget  *fc_origTarget;
		int			fc_attrno;

		if (fc_tle->resjunk)
		{
			/*
			 * Resjunk 节点无需额外处理，但请确保它们
			 * 具有不匹配任何目标列的 resnos；否则重写器
			 * 或规划者可能会感到困惑。它们也不需要 resname。
			 */
			fc_tle->resno = (AttrNumber) fc_pstate->p_next_resno++;
			fc_tle->resname = NULL;
			continue;
		}
		if (fc_orig_tl == NULL)
			elog(ERROR, "UPDATE target count mismatch --- internal error");
		fc_origTarget = lfirst_node(ResTarget, fc_orig_tl);

		fc_attrno = attnameAttNum(fc_pstate->p_target_relation,
							   fc_origTarget->name, true);
		if (fc_attrno == InvalidAttrNumber)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" of relation \"%s\" does not exist",
							fc_origTarget->name,
							RelationGetRelationName(fc_pstate->p_target_relation)),
					 parser_errposition(fc_pstate, fc_origTarget->location)));

		updateTargetListEntry(fc_pstate, fc_tle, fc_origTarget->name,
							  fc_attrno,
							  fc_origTarget->indirection,
							  fc_origTarget->location);

		/* 标记目标列为需要更新权限 */
		fc_target_rte->updatedCols = bms_add_member(fc_target_rte->updatedCols,
												 fc_attrno - FirstLowInvalidHeapAttributeNumber);

		fc_orig_tl = lnext(fc_origTlist, fc_orig_tl);
	}
	if (fc_orig_tl != NULL)
		elog(ERROR, "UPDATE target count mismatch --- internal error");

	return fc_tlist;
}

/*
 * transformReturningList -
 *	处理 INSERT/UPDATE/DELETE 中的 RETURNING 子句
 */
static List * fc_transformReturningList(ParseState *fc_pstate, List *fc_returningList)
{
	List	   *fc_rlist;
	int			fc_save_next_resno;

	if (fc_returningList == NIL)
		return NIL;				/* 无需操作 */

	/*
	 * 我们需要从 RETURNING 列表中的一开始分配 resnos。保存
	 * 并恢复主 tlist 的 p_next_resno 值，以防
	 * 后面有人查看它（可能不会发生）。
	 */
	fc_save_next_resno = fc_pstate->p_next_resno;
	fc_pstate->p_next_resno = 1;

	/* 将 RETURNING 转换为与 SELECT 目标列表相同 */
	fc_rlist = transformTargetList(fc_pstate, fc_returningList, EXPR_KIND_RETURNING);

	/*
	 * 如果非空的 tlist 扩展为空（如果它仅包含零列表的星扩展
	 * 就是有可能的），则进行投诉。如果我们允许这样，解析的查询
	 * 看起来好像没有 RETURNING，结果可能会让用户感到惊讶。
	 */
	if (fc_rlist == NIL)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("RETURNING must have at least one column"),
				 parser_errposition(fc_pstate,
									exprLocation(linitial(fc_returningList)))));

	/* 标记列的来源 */
	markTargetListOrigins(fc_pstate, fc_rlist);

	/* 将任何仍未解析的输出列解析为文本类型 */
	if (fc_pstate->p_resolve_unknowns)
		resolveTargetListUnknowns(fc_pstate, fc_rlist);

	/* 恢复状态 */
	fc_pstate->p_next_resno = fc_save_next_resno;

	return fc_rlist;
}


/*
 * transformPLAssignStmt -
 *	  转换 PL/pgSQL 赋值语句
 *
 * 如果没有 opt_indirection，转换后的语句看起来像
 * "SELECT a_expr ..."，除了表达式已被转换为目标的类型。
 * 有了间接引用，它仍然是一个 SELECT，但表达式将
 * 包含 FieldStore 和/或赋值 SubscriptingRef 节点，以计算
 * 表示目标的容器类型变量的新值。该表达式将目标
 * 作为容器源进行引用。
 */
static Query * fc_transformPLAssignStmt(ParseState *fc_pstate, PLAssignStmt *fc_stmt)
{
	Query	   *fc_qry = makeNode(Query);
	ColumnRef  *fc_cref = makeNode(ColumnRef);
	List	   *fc_indirection = fc_stmt->indirection;
	int			fc_nnames = fc_stmt->nnames;
	SelectStmt *fc_sstmt = fc_stmt->val;
	Node	   *fc_target;
	Oid			fc_targettype;
	int32		fc_targettypmod;
	Oid			fc_targetcollation;
	List	   *fc_tlist;
	TargetEntry *fc_tle;
	Oid			fc_type_id;
	Node	   *fc_qual;
	ListCell   *fc_l;

	/*
	 * 首先，为目标变量构造一个 ColumnRef。如果目标
	 * 具有多个带点的名称，我们必须从
	 * 间接引用列表中提取额外的名称。
	 */
	fc_cref->fields = list_make1(makeString(fc_stmt->name));
	fc_cref->location = fc_stmt->location;
	if (fc_nnames > 1)
	{
		/* 避免破坏原始解析树 */
		fc_indirection = list_copy(fc_indirection);
		while (--fc_nnames > 0 && fc_indirection != NIL)
		{
			Node	   *fc_ind = (Node *) linitial(fc_indirection);

			if (!IsA(fc_ind, String))
				elog(ERROR, "invalid name count in PLAssignStmt");
			fc_cref->fields = lappend(fc_cref->fields, fc_ind);
			fc_indirection = list_delete_first(fc_indirection);
		}
	}

	/*
	 * 转换目标引用。通常我们将获得一个 Param
	 * 节点，但没有理由对其类型过于挑剔。
	 */
	fc_target = transformExpr(fc_pstate, (Node *) fc_cref,
						   EXPR_KIND_UPDATE_TARGET);
	fc_targettype = exprType(fc_target);
	fc_targettypmod = exprTypmod(fc_target);
	fc_targetcollation = exprCollation(fc_target);

	/*
	 * 其余部分大部分与 transformSelectStmt 匹配，只是我们
	 * 不需要考虑 WITH 或 INTO，并且我们以自己的方式构建目标列表。
	 */
	fc_qry->commandType = CMD_SELECT;
	fc_pstate->p_is_insert = false;

	/* 使 FOR UPDATE/FOR SHARE 信息可用于 addRangeTableEntry */
	fc_pstate->p_locking_clause = fc_sstmt->lockingClause;

	/* 使 WINDOW 信息也可用于窗口函数 */
	fc_pstate->p_windowdefs = fc_sstmt->windowClause;

	/* 处理 FROM 子句 */
	transformFromClause(fc_pstate, fc_sstmt->fromClause);

	/* 最初将目标列表转换为类似 SELECT 的方式 */
	fc_tlist = transformTargetList(fc_pstate, fc_sstmt->targetList,
								EXPR_KIND_SELECT_TARGET);

	/* 我们应该有确切的一个目标列表项 */
	if (list_length(fc_tlist) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg_plural("assignment source returned %d column",
							   "assignment source returned %d columns",
							   list_length(fc_tlist),
							   list_length(fc_tlist))));

	fc_tle = linitial_node(TargetEntry, fc_tlist);

	/*
	 * 下面这一部分类似于 transformAssignedExpr；关键区别
	 * 是我们使用 COERCION_PLPGSQL 而不是 COERCION_ASSIGNMENT。
	 */
	fc_type_id = exprType((Node *) fc_tle->expr);

	fc_pstate->p_expr_kind = EXPR_KIND_UPDATE_TARGET;

	if (fc_indirection)
	{
		fc_tle->expr = (Expr *)
			transformAssignmentIndirection(fc_pstate,
										   fc_target,
										   fc_stmt->name,
										   false,
										   fc_targettype,
										   fc_targettypmod,
										   fc_targetcollation,
										   fc_indirection,
										   list_head(fc_indirection),
										   (Node *) fc_tle->expr,
										   COERCION_PLPGSQL,
										   exprLocation(fc_target));
	}
	else if (fc_targettype != fc_type_id &&
			 (fc_targettype == RECORDOID || ISCOMPLEX(fc_targettype)) &&
			 (fc_type_id == RECORDOID || ISCOMPLEX(fc_type_id)))
	{
		/*
		 * Hack: 不让 coerce_to_target_type() 处理不一致
		 * 复合类型。只需将表达式结果按原样传递，
		 * 让 PL/pgSQL 执行器以其方式进行转换。这是
		 * 相当不够规范，但为了向后兼容这是必要的。
		 */
	}
	else
	{
		/*
		 * 对于普通的非合格目标列，进行类型检查和
		 * 强制转换。
		 */
		Node	   *fc_orig_expr = (Node *) fc_tle->expr;

		fc_tle->expr = (Expr *)
			coerce_to_target_type(fc_pstate,
								  fc_orig_expr, fc_type_id,
								  fc_targettype, fc_targettypmod,
								  COERCION_PLPGSQL,
								  COERCE_IMPLICIT_CAST,
								  -1);
		/* 使用 COERCION_PLPGSQL，此错误可能无法达到 */
		if (fc_tle->expr == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("variable \"%s\" is of type %s"
							" but expression is of type %s",
							fc_stmt->name,
							format_type_be(fc_targettype),
							format_type_be(fc_type_id)),
					 errhint("You will need to rewrite or cast the expression."),
					 parser_errposition(fc_pstate, exprLocation(fc_orig_expr))));
	}

	fc_pstate->p_expr_kind = EXPR_KIND_NONE;

	fc_qry->targetList = list_make1(fc_tle);

	/* 转换 WHERE */
	fc_qual = transformWhereClause(fc_pstate, fc_sstmt->whereClause,
								EXPR_KIND_WHERE, "WHERE");

	/* HAVING 子句的初步处理与 WHERE 子句非常相似 */
	fc_qry->havingQual = transformWhereClause(fc_pstate, fc_sstmt->havingClause,
										   EXPR_KIND_HAVING, "HAVING");

	/* 
	 * 转换排序/分组内容。首先执行 ORDER BY，因为 transformGroupClause 
	 * 和 transformDistinctClause 都需要结果。注意这些函数也可以改变 
	 * targetList，因此通过引用将其传递给它们。
	 */
	fc_qry->sortClause = transformSortClause(fc_pstate,
										  fc_sstmt->sortClause,
										  &fc_qry->targetList,
										  EXPR_KIND_ORDER_BY,
										  false /* 允许 SQL92 规则 */ );

	fc_qry->groupClause = transformGroupClause(fc_pstate,
											fc_sstmt->groupClause,
											&fc_qry->groupingSets,
											&fc_qry->targetList,
											fc_qry->sortClause,
											EXPR_KIND_GROUP_BY,
											false /* 允许 SQL92 规则 */ );

	if (fc_sstmt->distinctClause == NIL)
	{
		fc_qry->distinctClause = NIL;
		fc_qry->hasDistinctOn = false;
	}
	else if (linitial(fc_sstmt->distinctClause) == NULL)
	{
		/* 我们有 SELECT DISTINCT */
		fc_qry->distinctClause = transformDistinctClause(fc_pstate,
													  &fc_qry->targetList,
													  fc_qry->sortClause,
													  false);
		fc_qry->hasDistinctOn = false;
	}
	else
	{
		/* 我们有 SELECT DISTINCT ON */
		fc_qry->distinctClause = transformDistinctOnClause(fc_pstate,
														fc_sstmt->distinctClause,
														&fc_qry->targetList,
														fc_qry->sortClause);
		fc_qry->hasDistinctOn = true;
	}

	/* 转换 LIMIT */
	fc_qry->limitOffset = transformLimitClause(fc_pstate, fc_sstmt->limitOffset,
											EXPR_KIND_OFFSET, "OFFSET",
											fc_sstmt->limitOption);
	fc_qry->limitCount = transformLimitClause(fc_pstate, fc_sstmt->limitCount,
										   EXPR_KIND_LIMIT, "LIMIT",
										   fc_sstmt->limitOption);
	fc_qry->limitOption = fc_sstmt->limitOption;

	/* 在查看所有窗口函数之后转换窗口子句 */
	fc_qry->windowClause = transformWindowDefinitions(fc_pstate,
												   fc_pstate->p_windowdefs,
												   &fc_qry->targetList);

	fc_qry->rtable = fc_pstate->p_rtable;
	fc_qry->jointree = makeFromExpr(fc_pstate->p_joinlist, fc_qual);

	fc_qry->hasSubLinks = fc_pstate->p_hasSubLinks;
	fc_qry->hasWindowFuncs = fc_pstate->p_hasWindowFuncs;
	fc_qry->hasTargetSRFs = fc_pstate->p_hasTargetSRFs;
	fc_qry->hasAggs = fc_pstate->p_hasAggs;

	foreach(fc_l, fc_sstmt->lockingClause)
	{
		fc_transformLockingClause(fc_pstate, fc_qry,
							   (LockingClause *) lfirst(fc_l), false);
	}

	assign_query_collations(fc_pstate, fc_qry);

	/* 必须在排序后完成，以确保可靠的表达式比较 */
	if (fc_pstate->p_hasAggs || fc_qry->groupClause || fc_qry->groupingSets || fc_qry->havingQual)
		parseCheckAggregates(fc_pstate, fc_qry);

	return fc_qry;
}


/*
 * transformDeclareCursorStmt -
 *	转换 DECLARE CURSOR 语句
 *
 * DECLARE CURSOR 类似于其他实用程序语句，因为我们以
 * CMD_UTILITY 查询节点的形式输出它；但是，我们必须首先
 * 转换包含的查询。我们过去将其推迟到执行时，
 * 但在正常解析分析阶段进行处理实际上是必要的，以确保
 * 解析器钩子的副作用在预期的时间发生。
 */
static Query * fc_transformDeclareCursorStmt(ParseState *fc_pstate, DeclareCursorStmt *fc_stmt)
{
	Query	   *fc_result;
	Query	   *fc_query;

	if ((fc_stmt->options & CURSOR_OPT_SCROLL) &&
		(fc_stmt->options & CURSOR_OPT_NO_SCROLL))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
		/* 翻译者：%s 是一个 SQL 关键字 */
				 errmsg("cannot specify both %s and %s",
						"SCROLL", "NO SCROLL")));

	if ((fc_stmt->options & CURSOR_OPT_ASENSITIVE) &&
		(fc_stmt->options & CURSOR_OPT_INSENSITIVE))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
		/* 翻译者：%s 是一个 SQL 关键字 */
				 errmsg("cannot specify both %s and %s",
						"ASENSITIVE", "INSENSITIVE")));

	/* 转换包含的查询，不允许 SELECT INTO */
	fc_query = transformStmt(fc_pstate, fc_stmt->query);
	fc_stmt->query = (Node *) fc_query;

	/* 语法不应允许除了 SELECT 以外的任何内容 */
	if (!IsA(fc_query, Query) ||
		fc_query->commandType != CMD_SELECT)
		elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR");

	/*
	 * 我们还不允许在游标中使用会修改数据的 WITH。 （这可能被允许，但何时进行更新的语义可能令人惊讶。）
	 */
	if (fc_query->hasModifyingCTE)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("DECLARE CURSOR must not contain data-modifying statements in WITH")));

	/* FOR UPDATE 和 WITH HOLD 不是兼容的 */
	if (fc_query->rowMarks != NIL && (fc_stmt->options & CURSOR_OPT_HOLD))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("DECLARE CURSOR WITH HOLD ... %s is not supported",
						LCS_asString(((RowMarkClause *)
									  linitial(fc_query->rowMarks))->strength)),
				 errdetail("Holdable cursors must be READ ONLY.")));

	/* FOR UPDATE 和 SCROLL 不是兼容的 */
	if (fc_query->rowMarks != NIL && (fc_stmt->options & CURSOR_OPT_SCROLL))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("DECLARE SCROLL CURSOR ... %s is not supported",
						LCS_asString(((RowMarkClause *)
									  linitial(fc_query->rowMarks))->strength)),
				 errdetail("Scrollable cursors must be READ ONLY.")));

	/* FOR UPDATE 和 INSENSITIVE 不是兼容的 */
	if (fc_query->rowMarks != NIL && (fc_stmt->options & CURSOR_OPT_INSENSITIVE))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("DECLARE INSENSITIVE CURSOR ... %s is not valid",
						LCS_asString(((RowMarkClause *)
									  linitial(fc_query->rowMarks))->strength)),
				 errdetail("Insensitive cursors must be READ ONLY.")));

	/* 将命令表示为一个实用查询 */
	fc_result = makeNode(Query);
	fc_result->commandType = CMD_UTILITY;
	fc_result->utilityStmt = (Node *) fc_stmt;

	return fc_result;
}


/*
 * transformExplainStmt -
 *	转换 EXPLAIN 语句
 *
 * EXPLAIN 像其他实用语句一样，我们将其作为 CMD_UTILITY 查询节点输出；但是，我们必须首先转换所包含的查询。 我们曾经将此推迟到执行时，但在正常解析分析阶段进行转换是非常必要的，以确保解析器钩子的副作用在预期时间发生。
 */
static Query * fc_transformExplainStmt(ParseState *fc_pstate, ExplainStmt *fc_stmt)
{
	Query	   *fc_result;

	/* 转换包含的查询，允许 SELECT INTO */
	fc_stmt->query = (Node *) fc_transformOptionalSelectInto(fc_pstate, fc_stmt->query);

	/* 将命令表示为一个实用查询 */
	fc_result = makeNode(Query);
	fc_result->commandType = CMD_UTILITY;
	fc_result->utilityStmt = (Node *) fc_stmt;

	return fc_result;
}


/*
 * transformCreateTableAsStmt -
 *	转换 CREATE TABLE AS、SELECT ... INTO 或 CREATE MATERIALIZED VIEW 语句
 *
 * 和 DECLARE CURSOR 及 EXPLAIN 一样，现在转换包含的语句。
 */
static Query * fc_transformCreateTableAsStmt(ParseState *fc_pstate, CreateTableAsStmt *fc_stmt)
{
	Query	   *fc_result;
	Query	   *fc_query;

	/* 转换包含的查询，不允许 SELECT INTO */
	fc_query = transformStmt(fc_pstate, fc_stmt->query);
	fc_stmt->query = (Node *) fc_query;

	/* CREATE MATERIALIZED VIEW 需要额外工作 */
	if (fc_stmt->objtype == OBJECT_MATVIEW)
	{
		/*
		 * 在创建物化视图的查询中禁止数据修改 CTE。 用户在刷新或增量维护 MV 时希望发生什么并不十分明确。
		 */
		if (fc_query->hasModifyingCTE)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("materialized views must not use data-modifying statements in WITH")));

		/*
		 * 检查创建查询中是否使用了任何临时数据库对象。 如果某个源消失，刷新数据或增量维护将变得困难。
		 */
		if (isQueryUsingTempRelation(fc_query))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("materialized views must not use temporary tables or views")));

		/*
		 * 物化视图要么需要保存用于维护/加载数据的参数，要么完全禁止它们。 后者似乎更安全，更理智。
		 */
		if (query_contains_extern_params(fc_query))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("materialized views may not be defined using bound parameters")));

		/*
		 * 目前，我们不允许未记录的物化视图，因为在崩溃后让它们变为空似乎是个坏主意。（如果我们能将它们标记为未填充，那就更好了，但这需要目录更改，而崩溃恢复目前无法处理。）
		 */
		if (fc_stmt->into->rel->relpersistence == RELPERSISTENCE_UNLOGGED)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("materialized views cannot be unlogged")));

		/*
		 * 在运行时，我们需要一个解析但未重写的查询的副本，以创建视图的 ON SELECT 规则。 我们将其存储在 IntoClause 中，因为 intorel_startup() 可以方便地从中获取它。
		 */
		fc_stmt->into->viewQuery = (Node *) copyObject(fc_query);
	}

	/* 将命令表示为一个实用查询 */
	fc_result = makeNode(Query);
	fc_result->commandType = CMD_UTILITY;
	fc_result->utilityStmt = (Node *) fc_stmt;

	return fc_result;
}

/*
 * 转换 CallStmt
 */
static Query * fc_transformCallStmt(ParseState *fc_pstate, CallStmt *fc_stmt)
{
	List	   *fc_targs;
	ListCell   *fc_lc;
	Node	   *fc_node;
	FuncExpr   *fc_fexpr;
	HeapTuple	fc_proctup;
	Datum		fc_proargmodes;
	bool		fc_isNull;
	List	   *fc_outargs = NIL;
	Query	   *fc_result;

	/*
	 * 首先，对过程调用及其参数进行标准解析分析，以便能够识别被调用的过程。
	 */
	fc_targs = NIL;
	foreach(fc_lc, fc_stmt->funccall->args)
	{
		fc_targs = lappend(fc_targs, transformExpr(fc_pstate,
											 (Node *) lfirst(fc_lc),
											 EXPR_KIND_CALL_ARGUMENT));
	}

	fc_node = ParseFuncOrColumn(fc_pstate,
							 fc_stmt->funccall->funcname,
							 fc_targs,
							 fc_pstate->p_last_srf,
							 fc_stmt->funccall,
							 true,
							 fc_stmt->funccall->location);

	assign_expr_collations(fc_pstate, fc_node);

	fc_fexpr = castNode(FuncExpr, fc_node);

	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_fexpr->funcid));
	if (!HeapTupleIsValid(fc_proctup))
		elog(ERROR, "cache lookup failed for function %u", fc_fexpr->funcid);

	/*
	 * 扩展参数列表以处理命名参数符号和默认参数。 对于普通的 FuncExprs，这将在规划过程中完成，但 CallStmt 不经过规划，并且没有好的理由不在这里完成。
	 */
	fc_fexpr->args = expand_function_arguments(fc_fexpr->args,
											true,
											fc_fexpr->funcresulttype,
											fc_proctup);

	/* 获取 proargmodes；如果为空，则没有输出参数 */
	fc_proargmodes = SysCacheGetAttr(PROCOID, fc_proctup,
								  Anum_pg_proc_proargmodes,
								  &fc_isNull);
	if (!fc_isNull)
	{
		/*
		 * 将列表拆分为 fexpr->args 中的输入参数和 stmt->outargs 中的输出参数。 INOUT 参数出现在两个列表中。
		 */
		ArrayType  *fc_arr;
		int			fc_numargs;
		char	   *fc_argmodes;
		List	   *fc_inargs;
		int			fc_i;

		fc_arr = DatumGetArrayTypeP(fc_proargmodes);	/* 确保未被压缩 */
		fc_numargs = list_length(fc_fexpr->args);
		if (ARR_NDIM(fc_arr) != 1 ||
			ARR_DIMS(fc_arr)[0] != fc_numargs ||
			ARR_HASNULL(fc_arr) ||
			ARR_ELEMTYPE(fc_arr) != CHAROID)
			elog(ERROR, "proargmodes is not a 1-D char array of length %d or it contains nulls",
				 fc_numargs);
		fc_argmodes = (char *) ARR_DATA_PTR(fc_arr);

		fc_inargs = NIL;
		fc_i = 0;
		foreach(fc_lc, fc_fexpr->args)
		{
			Node	   *fc_n = lfirst(fc_lc);

			switch (fc_argmodes[fc_i])
			{
				case PROARGMODE_IN:
				case PROARGMODE_VARIADIC:
					fc_inargs = lappend(fc_inargs, fc_n);
					break;
				case PROARGMODE_OUT:
					fc_outargs = lappend(fc_outargs, fc_n);
					break;
				case PROARGMODE_INOUT:
					fc_inargs = lappend(fc_inargs, fc_n);
					fc_outargs = lappend(fc_outargs, copyObject(fc_n));
					break;
				default:
					/* 注意我们不支持 PROARGMODE_TABLE */
					elog(ERROR, "invalid argmode %c for procedure",
						 fc_argmodes[fc_i]);
					break;
			}
			fc_i++;
		}
		fc_fexpr->args = fc_inargs;
	}

	fc_stmt->funcexpr = fc_fexpr;
	fc_stmt->outargs = fc_outargs;

	ReleaseSysCache(fc_proctup);

	/* 将命令表示为一个实用查询 */
	fc_result = makeNode(Query);
	fc_result->commandType = CMD_UTILITY;
	fc_result->utilityStmt = (Node *) fc_stmt;

	return fc_result;
}

/*
 * 生成 LockClauseStrength 值的字符串表示。 
 * 这应该仅应用于有效值（非 LCS_NONE）。
 */
const char * LCS_asString(LockClauseStrength fc_strength)
{
	switch (fc_strength)
	{
		case LCS_NONE:
			Assert(false);
			break;
		case LCS_FORKEYSHARE:
			return "FOR KEY SHARE";
		case LCS_FORSHARE:
			return "FOR SHARE";
		case LCS_FORNOKEYUPDATE:
			return "FOR NO KEY UPDATE";
		case LCS_FORUPDATE:
			return "FOR UPDATE";
	}
	return "FOR some";			/* 不应该发生 */
}

/*
 * 检查不支持 FOR [KEY] UPDATE/SHARE 的特性。
 *
 * 导出，以便规划器在重写、查询提升等后进行再次检查。
 */
void CheckSelectLocking(Query *fc_qry, LockClauseStrength fc_strength)
{
	Assert(fc_strength != LCS_NONE);	/*
		 * 我们使用 CaseTestExpr 作为强制转换树的基础，
		 * 因为插入源值是非常便宜的。
		 */

	if (fc_qry->setOperations)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
						LCS_asString(fc_strength))));
	if (fc_qry->distinctClause != NIL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("%s is not allowed with DISTINCT clause",
						LCS_asString(fc_strength))));
	if (fc_qry->groupClause != NIL || fc_qry->groupingSets != NIL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("%s is not allowed with GROUP BY clause",
						LCS_asString(fc_strength))));
	if (fc_qry->havingQual != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("%s is not allowed with HAVING clause",
						LCS_asString(fc_strength))));
	if (fc_qry->hasAggs)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("%s is not allowed with aggregate functions",
						LCS_asString(fc_strength))));
	if (fc_qry->hasWindowFuncs)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("%s is not allowed with window functions",
						LCS_asString(fc_strength))));
	if (fc_qry->hasTargetSRFs)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
		/*------
		  翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
				 errmsg("%s is not allowed with set-returning functions in the target list",
						LCS_asString(fc_strength))));
}

/*
 * 转换 FOR [KEY] UPDATE/SHARE 子句
 *
 * 这基本上涉及用整数 relids 替换名称。
 *
 * 注意：如果您需要更改此内容，请参见 rewriteHandler.c 中的 markQueryForLocking() 和 parse_relation.c 中的 isLockedRefname()。
 */
static void fc_transformLockingClause(ParseState *fc_pstate, Query *fc_qry, LockingClause *fc_lc,
					   bool fc_pushedDown)
{
	List	   *fc_lockedRels = fc_lc->lockedRels;
	ListCell   *fc_l;
	ListCell   *fc_rt;
	Index		fc_i;
	LockingClause *fc_allrels;

	CheckSelectLocking(fc_qry, fc_lc->strength);

	/* 制作一个可以传递给子查询以选择所有关系的子句 */
	fc_allrels = makeNode(LockingClause);
	fc_allrels->lockedRels = NIL;	/* 表示所有关系 */
	fc_allrels->strength = fc_lc->strength;
	fc_allrels->waitPolicy = fc_lc->waitPolicy;

	if (fc_lockedRels == NIL)
	{
		/*
		 * 锁定查询及其子查询中使用的所有常规表。 我们检查 inFromCl 以排除自动添加的 RTE，特别是规则中的 NEW/OLD。 这有点滥用一个大多数情况下已过时的标志，但这很方便。 我们不能依赖大多数情况下已取代 inFromCl 的命名空间机制，因为例如即使是被上层连接屏蔽的基本关系 RTE，我们也需要锁定它们。
		 */
		fc_i = 0;
		foreach(fc_rt, fc_qry->rtable)
		{
			RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_rt);

			++fc_i;
			if (!fc_rte->inFromCl)
				continue;
			switch (fc_rte->rtekind)
			{
				case RTE_RELATION:
					applyLockingClause(fc_qry, fc_i, fc_lc->strength, fc_lc->waitPolicy,
									   fc_pushedDown);
					fc_rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
					break;
				case RTE_SUBQUERY:
					applyLockingClause(fc_qry, fc_i, fc_lc->strength, fc_lc->waitPolicy,
									   fc_pushedDown);

					/*
					 * 子查询的 FOR UPDATE/SHARE 被传播到所有子查询的关系。 我们可以稍后进行此操作（基于子查询 RTE 的标记），但在每个查询级别获取哪些关系需要使用 RowShareLock 打开的本地信息是方便的。
					 */
					fc_transformLockingClause(fc_pstate, fc_rte->subquery,
										   fc_allrels, true);
					break;
				default:
					/* 忽略JOIN，SPECIAL，FUNCTION，VALUES，CTE RTEs */
					break;
			}
		}
	}
	else
	{
		/*
		 * 锁定指定的表。如上所述，我们允许锁定任何基础
		 * 关系，而不管别名可见性规则，因此我们需要
		 * 检查 inFromCl 以排除 OLD/NEW。
		 */
		foreach(fc_l, fc_lockedRels)
		{
			RangeVar   *fc_thisrel = (RangeVar *) lfirst(fc_l);

			/* 为简单起见，我们在这里坚持使用非限定别名 */
			if (fc_thisrel->catalogname || fc_thisrel->schemaname)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
				/*------
				  翻译者：%s 是一个 SQL 行锁定子句，比如 FOR UPDATE */
						 errmsg("%s must specify unqualified relation names",
								LCS_asString(fc_lc->strength)),
						 parser_errposition(fc_pstate, fc_thisrel->location)));

			fc_i = 0;
			foreach(fc_rt, fc_qry->rtable)
			{
				RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_rt);
				char	   *fc_rtename;

				++fc_i;
				if (!fc_rte->inFromCl)
					continue;

				/*
				 * 没有别名的连接 RTE 作为关系
				 * 名称不可见，需跳过（否则可能会隐藏一个
				 * 具有相同名称的基础关系），除非它具有一个 USING
				 * 别名，这 *是* 可见的。
				 */
				if (fc_rte->rtekind == RTE_JOIN && fc_rte->alias == NULL)
				{
					if (fc_rte->join_using_alias == NULL)
						continue;
					fc_rtename = fc_rte->join_using_alias->aliasname;
				}
				else
					fc_rtename = fc_rte->eref->aliasname;

				if (strcmp(fc_rtename, fc_thisrel->relname) == 0)
				{
					switch (fc_rte->rtekind)
					{
						case RTE_RELATION:
							applyLockingClause(fc_qry, fc_i, fc_lc->strength,
											   fc_lc->waitPolicy, fc_pushedDown);
							fc_rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
							break;
						case RTE_SUBQUERY:
							applyLockingClause(fc_qry, fc_i, fc_lc->strength,
											   fc_lc->waitPolicy, fc_pushedDown);
							/* 请参见上面的注释 */
							fc_transformLockingClause(fc_pstate, fc_rte->subquery,
												   fc_allrels, true);
							break;
						case RTE_JOIN:
							ereport(ERROR,
									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							/*------
							  翻译者：%s 是一个 SQL 行锁定子句，比如 FOR UPDATE */
									 errmsg("%s cannot be applied to a join",
											LCS_asString(fc_lc->strength)),
									 parser_errposition(fc_pstate, fc_thisrel->location)));
							break;
						case RTE_FUNCTION:
							ereport(ERROR,
									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							/*------
							  翻译者：%s 是一个 SQL 行锁定子句，比如 FOR UPDATE */
									 errmsg("%s cannot be applied to a function",
											LCS_asString(fc_lc->strength)),
									 parser_errposition(fc_pstate, fc_thisrel->location)));
							break;
						case RTE_TABLEFUNC:
							ereport(ERROR,
									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							/*------
							  翻译者：%s 是一个 SQL 行锁定子句，比如 FOR UPDATE */
									 errmsg("%s cannot be applied to a table function",
											LCS_asString(fc_lc->strength)),
									 parser_errposition(fc_pstate, fc_thisrel->location)));
							break;
						case RTE_VALUES:
							ereport(ERROR,
									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							/*------
							  翻译者：%s 是一个 SQL 行锁定子句，比如 FOR UPDATE */
									 errmsg("%s cannot be applied to VALUES",
											LCS_asString(fc_lc->strength)),
									 parser_errposition(fc_pstate, fc_thisrel->location)));
							break;
						case RTE_CTE:
							ereport(ERROR,
									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							/*------
							  翻译者：%s 是一个 SQL 行锁定子句，比如 FOR UPDATE */
									 errmsg("%s cannot be applied to a WITH query",
											LCS_asString(fc_lc->strength)),
									 parser_errposition(fc_pstate, fc_thisrel->location)));
							break;
						case RTE_NAMEDTUPLESTORE:
							ereport(ERROR,
									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							/*------
							  翻译者：%s 是一个 SQL 行锁定子句，比如 FOR UPDATE */
									 errmsg("%s cannot be applied to a named tuplestore",
											LCS_asString(fc_lc->strength)),
									 parser_errposition(fc_pstate, fc_thisrel->location)));
							break;

							/* 不应该能在这里看到 RTE_RESULT */

						default:
							elog(ERROR, "unrecognized RTE type: %d",
								 (int) fc_rte->rtekind);
							break;
					}
					break;		/* 超出 foreach 循环 */
				}
			}
			if (fc_rt == NULL)
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_TABLE),
				/*------
				  翻译者：%s 是一个 SQL 行锁定子句，比如 FOR UPDATE */
						 errmsg("relation \"%s\" in %s clause not found in FROM clause",
								fc_thisrel->relname,
								LCS_asString(fc_lc->strength)),
						 parser_errposition(fc_pstate, fc_thisrel->location)));
		}
	}
}

/*
 * 记录单个 rangetable 项目的锁定信息
 */
void applyLockingClause(Query *fc_qry, Index fc_rtindex,
				   LockClauseStrength fc_strength, LockWaitPolicy fc_waitPolicy,
				   bool fc_pushedDown)
{
	RowMarkClause *fc_rc;

	Assert(fc_strength != LCS_NONE);	/*
		 * 我们使用 CaseTestExpr 作为强制转换树的基础，
		 * 因为插入源值是非常便宜的。
		 */

	/* 如果这是一个显式子句，请确保 hasForUpdate 被设置 */
	if (!fc_pushedDown)
		fc_qry->hasForUpdate = true;

	/* 检查是否存在相同 rtindex 的先前条目 */
	if ((fc_rc = get_parse_rowmark(fc_qry, fc_rtindex)) != NULL)
	{
		/*
		 * 如果同一个 RTE 被指定了多个锁定强度，
		 * 请使用最强的。（这是合理的，因为你不能同时
		 * 获取共享和排他锁；最终仍会是排他锁。）
		 *
		 * 类似地，如果同一个 RTE 被指定了多个锁定
		 * 等待策略，请认为 NOWAIT 胜过 SKIP LOCKED，而
		 * SKIP LOCKED 又胜过等待锁（默认情况）。这在
		 * 辩论上有点争议，但抛出错误似乎没什么帮助。
		 * （例如，考虑从一个内部包含普通 FOR UPDATE
		 * 规范的视图中SELECT FOR UPDATE NOWAIT。）让 NOWAIT 胜过 SKIP
		 * LOCKED 是合理的，因为前者在遇到一个锁定的元组时会抛出错误，
		 * 这在某些情况下可能是不可取的，但似乎比默默地返回不一致
		 * 的结果要好。
		 *
		 * 当然如果任何子句是显式的，pushedDown 将变为 false。
		 */
		fc_rc->strength = Max(fc_rc->strength, fc_strength);
		fc_rc->waitPolicy = Max(fc_rc->waitPolicy, fc_waitPolicy);
		fc_rc->pushedDown &= fc_pushedDown;
		return;
	}

	/* 创建一个新的 RowMarkClause */
	fc_rc = makeNode(RowMarkClause);
	fc_rc->rti = fc_rtindex;
	fc_rc->strength = fc_strength;
	fc_rc->waitPolicy = fc_waitPolicy;
	fc_rc->pushedDown = fc_pushedDown;
	fc_qry->rowMarks = lappend(fc_qry->rowMarks, fc_rc);
}

/*
 * 对 raw_expression_tree_walker() 进行覆盖测试。
 *
 * 开启时，我们对提交给解析分析的每个 DML 语句
 * 运行 raw_expression_tree_walker()。没有这个规定，
 * 该函数仅在涉及 CTE 的有限情况下应用，而我们不希望
 * 测试 CTE 内部和外部的所有内容。
 */
#ifdef RAW_EXPRESSION_COVERAGE_TEST

static bool fc_test_raw_expression_coverage(Node *fc_node, void *fc_context)
{
	if (fc_node == NULL)
		return false;
	return raw_expression_tree_walker(fc_node,
									  fc_test_raw_expression_coverage,
									  fc_context);
}

#endif							/* RAW_EXPRESSION_COVERAGE_TEST */
